import { describe, it, expect, vi, beforeEach } from 'vitest';
import { updateLeague } from '~/models/league';
import { findCurrentSeasonWithSports, updateSeason } from '~/models/season';
import { parseDraftSpeed } from '~/lib/draft-timer';

vi.mock('~/models/league', () => ({
  findLeagueById: vi.fn(),
  updateLeague: vi.fn(),
  deleteLeague: vi.fn(),
}));

vi.mock('~/models/commissioner', () => ({
  isCommissioner: vi.fn(),
  findCommissionersByLeagueId: vi.fn(),
  createCommissioner: vi.fn(),
  countCommissionersByLeagueId: vi.fn(),
  removeCommissionerByLeagueAndUser: vi.fn(),
  hasCommissionerRecord: vi.fn(),
}));

vi.mock('~/models/season', () => ({
  findCurrentSeasonWithSports: vi.fn(),
  updateSeason: vi.fn(),
}));

vi.mock('~/models/team', () => ({
  findTeamsBySeasonId: vi.fn(),
  createManyTeams: vi.fn(),
  deleteTeam: vi.fn(),
  removeTeamOwner: vi.fn(),
  claimTeam: vi.fn(),
  findTeamById: vi.fn(),
  renameTeam: vi.fn(),
}));

const mockLeague = {
  id: 'league-1',
  name: 'Test League',
  createdBy: 'user-1',
  currentSeasonId: 'season-1',
  isPublicDraftBoard: false,
  discordWebhookUrl: null,
  discordPicksAnnouncementEnabled: false,
  createdAt: new Date('2025-01-01'),
  updatedAt: new Date('2025-01-01'),
};

const mockSeason = {
  id: 'season-1',
  leagueId: 'league-1',
  year: 2025,
  status: 'pre_draft' as const,
  templateId: null,
  draftRounds: 20,
  flexSpots: 0,
  draftDateTime: null,
  autoStartDraft: false,
  draftInitialTime: 120,
  draftIncrementTime: 15,
  draftTimerMode: 'chess_clock' as const,
  currentPickNumber: null,
  draftStartedAt: null,
  draftCompletedAt: null,
  draftPaused: false,
  overnightPauseMode: 'none' as const,
  overnightPauseStart: null,
  overnightPauseEnd: null,
  overnightPauseTimezone: null,
  inviteCode: 'TEST123',
  pointsFor1st: 100,
  pointsFor2nd: 70,
  pointsFor3rd: 50,
  pointsFor4th: 40,
  pointsFor5th: 25,
  pointsFor6th: 25,
  pointsFor7th: 15,
  pointsFor8th: 15,
  seasonSports: [],
  createdAt: new Date('2025-01-01'),
  updatedAt: new Date('2025-01-01'),
};

// Mirrors the route-level settings access policy: the settings page requires a
// commissioner or admin record — creator status alone is not sufficient.
function canAccessLeagueSettings(opts: {
  isAuthenticated: boolean;
  leagueExists: boolean;
  isCommissionerOrAdmin: boolean;
}): { allowed: true } | { allowed: false; status: 401 | 403 | 404 } {
  if (!opts.isAuthenticated) return { allowed: false, status: 401 };
  if (!opts.leagueExists) return { allowed: false, status: 404 };
  if (!opts.isCommissionerOrAdmin) return { allowed: false, status: 403 };
  return { allowed: true };
}

function canUpdateScoring(status: string) { return status === 'pre_draft'; }
function isValidScoringPoints(points: number) { return points >= 0 && points <= 1000; }
function isValidTeamCount(count: number) { return count >= 6 && count <= 16; }

function validateOwnerAssignment(opts: {
  assignedUserId: string;
  leagueMemberIds: string[];
  existingOwnerIds: string[];
}): { allowed: true } | { allowed: false; error: string } {
  if (!opts.leagueMemberIds.includes(opts.assignedUserId)) {
    return { allowed: false, error: 'Only current league members can be assigned to teams' };
  }
  if (opts.existingOwnerIds.includes(opts.assignedUserId)) {
    return { allowed: false, error: 'This user is already assigned to a team in this league' };
  }
  return { allowed: true };
}

// ---------------------------------------------------------------------------
// Access policy
// ---------------------------------------------------------------------------

describe('Settings Access Policy', () => {
  it('rejects unauthenticated users', () => {
    expect(canAccessLeagueSettings({
      isAuthenticated: false,
      leagueExists: true,
      isCommissionerOrAdmin: false,
    })).toEqual({ allowed: false, status: 401 });
  });

  it('rejects non-commissioner league members and creators', () => {
    expect(canAccessLeagueSettings({
      isAuthenticated: true,
      leagueExists: true,
      isCommissionerOrAdmin: false,
    })).toEqual({ allowed: false, status: 403 });
  });

  it('allows commissioners and admins via the shared commissioner check', () => {
    expect(canAccessLeagueSettings({
      isAuthenticated: true,
      leagueExists: true,
      isCommissionerOrAdmin: true,
    })).toEqual({ allowed: true });
  });
});

// ---------------------------------------------------------------------------
// Owner assignment scope
// ---------------------------------------------------------------------------

describe('Settings Update - Owner Assignment Scope', () => {
  it('rejects assigning someone who is not already attached to the league', () => {
    expect(validateOwnerAssignment({
      assignedUserId: 'outside-user',
      leagueMemberIds: ['commissioner-1', 'owner-1'],
      existingOwnerIds: ['owner-1'],
    })).toEqual({
      allowed: false,
      error: 'Only current league members can be assigned to teams',
    });
  });

  it('allows league commissioners without a team to be assigned', () => {
    expect(validateOwnerAssignment({
      assignedUserId: 'commissioner-1',
      leagueMemberIds: ['commissioner-1', 'owner-1'],
      existingOwnerIds: ['owner-1'],
    })).toEqual({ allowed: true });
  });
});

// ---------------------------------------------------------------------------
// Name validation
// ---------------------------------------------------------------------------

describe('Settings Update - Name Validation', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should reject an empty league name', () => {
    const name = '';
    const isInvalid = typeof name !== 'string' || !name.trim();
    expect(isInvalid).toBe(true);
    expect(updateLeague).not.toHaveBeenCalled();
  });

  it('should reject a whitespace-only league name', () => {
    const name = '   ';
    const isInvalid = typeof name !== 'string' || !name.trim();
    expect(isInvalid).toBe(true);
  });

  it('should reject a name shorter than 3 characters', () => {
    const name = 'AB';
    const trimmed = name.trim();
    expect(trimmed.length < 3 || trimmed.length > 50).toBe(true);
  });

  it('should reject a name longer than 50 characters', () => {
    const name = 'A'.repeat(51);
    const trimmed = name.trim();
    expect(trimmed.length < 3 || trimmed.length > 50).toBe(true);
  });

  it('should accept a name that is exactly 3 characters', () => {
    const name = 'ABC';
    const trimmed = name.trim();
    expect(trimmed.length >= 3 && trimmed.length <= 50).toBe(true);
  });

  it('should accept a name that is exactly 50 characters', () => {
    const name = 'A'.repeat(50);
    const trimmed = name.trim();
    expect(trimmed.length >= 3 && trimmed.length <= 50).toBe(true);
  });

  it('should trim whitespace from the name before saving', async () => {
    vi.mocked(updateLeague).mockResolvedValue({
      ...mockLeague,
      name: 'Trimmed Name',
    });

    await updateLeague('league-1', { name: '  Trimmed Name  '.trim(), isPublicDraftBoard: false });

    expect(updateLeague).toHaveBeenCalledWith('league-1', {
      name: 'Trimmed Name',
      isPublicDraftBoard: false,
    });
  });
});

// ---------------------------------------------------------------------------
// League-level save (first update block)
// ---------------------------------------------------------------------------

describe('Settings Update - League Save', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should save name and isPublicDraftBoard=false when checkbox is off', async () => {
    vi.mocked(updateLeague).mockResolvedValue({ ...mockLeague, name: 'New Name', isPublicDraftBoard: false });

    await updateLeague('league-1', { name: 'New Name', isPublicDraftBoard: false });

    expect(updateLeague).toHaveBeenCalledWith('league-1', {
      name: 'New Name',
      isPublicDraftBoard: false,
    });
  });

  it('should save isPublicDraftBoard=true when checkbox is on', async () => {
    vi.mocked(updateLeague).mockResolvedValue({ ...mockLeague, isPublicDraftBoard: true });

    await updateLeague('league-1', { name: 'Test League', isPublicDraftBoard: true });

    expect(updateLeague).toHaveBeenCalledWith('league-1', {
      name: 'Test League',
      isPublicDraftBoard: true,
    });
  });

  it('should return an error when updateLeague throws', async () => {
    const error = new Error('Database connection failed');
    vi.mocked(updateLeague).mockRejectedValue(error);

    let result: { error: string } | undefined;
    try {
      await updateLeague('league-1', { name: 'Test League', isPublicDraftBoard: false });
    } catch {
      result = { error: 'Failed to update league. Please try again.' };
    }

    expect(result).toEqual({ error: 'Failed to update league. Please try again.' });
  });

  it('should not reach season update when updateLeague throws', async () => {
    vi.mocked(updateLeague).mockRejectedValue(new Error('DB error'));
    vi.mocked(findCurrentSeasonWithSports).mockResolvedValue(mockSeason);

    try {
      await updateLeague('league-1', { name: 'Test', isPublicDraftBoard: false });
    } catch {
      // early return happens here in the real action
    }

    expect(findCurrentSeasonWithSports).not.toHaveBeenCalled();
    expect(updateSeason).not.toHaveBeenCalled();
  });

  it('should redirect when league saves successfully but no season exists', async () => {
    vi.mocked(updateLeague).mockResolvedValue(mockLeague);
    vi.mocked(findCurrentSeasonWithSports).mockResolvedValue(undefined);

    await updateLeague('league-1', { name: 'Test League', isPublicDraftBoard: false });
    const season = await findCurrentSeasonWithSports('league-1');

    expect(season).toBeUndefined();
    // In the real action this triggers redirect(`/leagues/league-1?updated=true`)
  });
});

// ---------------------------------------------------------------------------
// Season-level save (second update block)
// ---------------------------------------------------------------------------

describe('Settings Update - Season Save', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should update season with valid draft rounds', async () => {
    vi.mocked(updateLeague).mockResolvedValue(mockLeague);
    vi.mocked(findCurrentSeasonWithSports).mockResolvedValue(mockSeason);
    vi.mocked(updateSeason).mockResolvedValue({ ...mockSeason, draftRounds: 15 });

    await updateSeason('season-1', { draftRounds: 15 });

    expect(updateSeason).toHaveBeenCalledWith('season-1', { draftRounds: 15 });
  });

  it('should reject draft rounds below 1', () => {
    const rounds = 0;
    expect(rounds < 1 || rounds > 50).toBe(true);
  });

  it('should reject draft rounds above 50', () => {
    const rounds = 51;
    expect(rounds < 1 || rounds > 50).toBe(true);
  });

  it('should reject draft rounds less than the number of sports selected', () => {
    const sportsCount = 5;
    const draftRounds = 3;
    expect(draftRounds < sportsCount).toBe(true);
  });

  it('should map "fast" chess_clock speed to correct timer values', () => {
    const result = parseDraftSpeed('fast', 'chess_clock');
    expect(result).toEqual({ draftInitialTime: 60, draftIncrementTime: 10 });
  });

  it('should map "standard" chess_clock speed to correct timer values', () => {
    const result = parseDraftSpeed('standard', 'chess_clock');
    expect(result).toEqual({ draftInitialTime: 120, draftIncrementTime: 15 });
  });

  it('should map "slow" chess_clock speed to correct timer values', () => {
    const result = parseDraftSpeed('slow', 'chess_clock');
    expect(result).toEqual({ draftInitialTime: 28800, draftIncrementTime: 3600 });
  });

  it('should map "very-slow" chess_clock speed to correct timer values', () => {
    const result = parseDraftSpeed('very-slow', 'chess_clock');
    expect(result).toEqual({ draftInitialTime: 43200, draftIncrementTime: 3600 });
  });

  it('should fall back to standard speed for an unknown chess_clock speed value', () => {
    const result = parseDraftSpeed('turbo-ultra', 'chess_clock');
    expect(result).toEqual({ draftInitialTime: 120, draftIncrementTime: 15 });
  });

  it('should use raw seconds for standard timer mode', () => {
    const result = parseDraftSpeed('90', 'standard');
    expect(result).toEqual({ draftInitialTime: 90, draftIncrementTime: 90 });
  });

  it('should fall back to 90s when standard mode speed is not a number', () => {
    const result = parseDraftSpeed(null, 'standard');
    expect(result).toEqual({ draftInitialTime: 90, draftIncrementTime: 90 });
  });

  it('should return a season-specific error message when updateSeason throws', async () => {
    vi.mocked(updateSeason).mockRejectedValue(new Error('Season DB error'));

    let result: { error: string } | undefined;
    try {
      await updateSeason('season-1', { draftRounds: 10 });
    } catch {
      result = { error: 'Failed to update season settings. Please try again.' };
    }

    expect(result).toEqual({ error: 'Failed to update season settings. Please try again.' });
  });

  it('should not say "league" in the season error message', async () => {
    vi.mocked(updateSeason).mockRejectedValue(new Error('DB error'));

    let errorMessage = '';
    try {
      await updateSeason('season-1', { draftRounds: 5 });
    } catch {
      errorMessage = 'Failed to update season settings. Please try again.';
    }

    expect(errorMessage).not.toContain('update league');
  });

  it('should only update scoring fields when season status is pre_draft', () => {
    expect(canUpdateScoring('pre_draft')).toBe(true);
    expect(canUpdateScoring('draft')).toBe(false);
  });

  it('should validate scoring points are between 0 and 1000', () => {
    expect(isValidScoringPoints(500)).toBe(true);
    expect(isValidScoringPoints(-1)).toBe(false);
    expect(isValidScoringPoints(1001)).toBe(false);
  });
});

// ---------------------------------------------------------------------------
// Team count changes within the update intent
// ---------------------------------------------------------------------------

describe('Settings Update - Team Count Changes', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should reject team count below 6', () => {
    expect(isValidTeamCount(5)).toBe(false);
  });

  it('should reject team count above 16', () => {
    expect(isValidTeamCount(17)).toBe(false);
  });

  it('should reject team count below the number of teams with owners', () => {
    const teamsWithOwners = 8;
    const newTeamCount = 6;
    expect(newTeamCount < teamsWithOwners).toBe(true);
  });

  it('should allow a valid team count that is >= teams with owners', () => {
    const teamsWithOwners = 6;
    const newTeamCount = 10;
    expect(isValidTeamCount(newTeamCount) && newTeamCount >= teamsWithOwners).toBe(true);
  });

  it('should create new teams when increasing team count', async () => {
    const { createManyTeams } = await import('~/models/team');
    vi.mocked(createManyTeams).mockResolvedValue([]);

    const currentTeamCount = 8;
    const newTeamCount = 10;
    const teamsToAdd = Array.from(
      { length: newTeamCount - currentTeamCount },
      (_, i) => ({
        seasonId: 'season-1',
        name: `Team ${currentTeamCount + i + 1}`,
        ownerId: null,
      })
    );

    await createManyTeams(teamsToAdd);

    expect(createManyTeams).toHaveBeenCalledWith([
      { seasonId: 'season-1', name: 'Team 9', ownerId: null },
      { seasonId: 'season-1', name: 'Team 10', ownerId: null },
    ]);
  });
});

// ---------------------------------------------------------------------------
// Draft slot guard when increasing team count
// Mirrors the server action condition: existingSlots.length === currentTeamCount
// ---------------------------------------------------------------------------

function shouldAppendDraftSlots(existingSlotsCount: number, currentTeamCount: number): boolean {
  return existingSlotsCount === currentTeamCount;
}

describe('Settings Update - Draft Slot Creation Guard', () => {
  it('does not create slots when no draft order was ever set', () => {
    expect(shouldAppendDraftSlots(0, 12)).toBe(false);
  });

  it('creates slots when draft order is fully configured', () => {
    expect(shouldAppendDraftSlots(12, 12)).toBe(true);
  });

  it('does not create slots when existing slots are partial (legacy edge case)', () => {
    expect(shouldAppendDraftSlots(8, 12)).toBe(false);
  });

  it('appends new slots at consecutive positions after the current max', () => {
    const existingSlots = [{ draftOrder: 1 }, { draftOrder: 2 }, { draftOrder: 3 }];
    const maxOrder = existingSlots.reduce((max, s) => Math.max(max, s.draftOrder), 0);
    const newTeams = [{ id: 'team-4' }, { id: 'team-5' }];
    const newSlots = newTeams.map((team, i) => ({
      seasonId: 'season-1',
      teamId: team.id,
      draftOrder: maxOrder + i + 1,
    }));
    expect(newSlots).toEqual([
      { seasonId: 'season-1', teamId: 'team-4', draftOrder: 4 },
      { seasonId: 'season-1', teamId: 'team-5', draftOrder: 5 },
    ]);
  });
});

// ---------------------------------------------------------------------------
// Draft order action responses
// ---------------------------------------------------------------------------

describe('Settings - Draft Order Action Responses', () => {
  it('set-draft-order success includes section: draft-order', () => {
    const result = { success: true, message: 'Draft order updated successfully', section: 'draft-order' as const };
    expect(result.section).toBe('draft-order');
    expect(result.success).toBe(true);
  });

  it('randomize-draft-order success includes section: draft-order', () => {
    const result = { success: true, message: 'Draft order randomized successfully', section: 'draft-order' as const };
    expect(result.section).toBe('draft-order');
    expect(result.message).toContain('randomized');
  });

  it('draft-order messages should not be shown in global SettingsMessage', () => {
    const actionData = { success: true, message: 'Draft order randomized successfully', section: 'draft-order' as const };
    const isDraftOrderMessage = 'section' in actionData && actionData.section === 'draft-order';
    expect(isDraftOrderMessage).toBe(true);
  });
});

describe('discordPicksAnnouncementEnabled setting', () => {
  it('parses discordPicksAnnouncementEnabled as true when form value is "on"', () => {
    const formData = new FormData();
    formData.set('discordPicksAnnouncementEnabled', 'on');
    const value = formData.get('discordPicksAnnouncementEnabled') === 'on';
    expect(value).toBe(true);
  });

  it('parses discordPicksAnnouncementEnabled as false when field is absent', () => {
    const formData = new FormData();
    const value = formData.get('discordPicksAnnouncementEnabled') === 'on';
    expect(value).toBe(false);
  });

  it('updateLeague is called with discordPicksAnnouncementEnabled: true', async () => {
    vi.mocked(updateLeague).mockResolvedValue({ ...mockLeague, discordPicksAnnouncementEnabled: true });

    await updateLeague('league-1', { name: 'Test League', isPublicDraftBoard: false, discordPicksAnnouncementEnabled: true });

    expect(updateLeague).toHaveBeenCalledWith('league-1', {
      name: 'Test League',
      isPublicDraftBoard: false,
      discordPicksAnnouncementEnabled: true,
    });
  });

  it('updateLeague is called with discordPicksAnnouncementEnabled: false', async () => {
    vi.mocked(updateLeague).mockResolvedValue({ ...mockLeague, discordPicksAnnouncementEnabled: false });

    await updateLeague('league-1', { name: 'Test League', isPublicDraftBoard: false, discordPicksAnnouncementEnabled: false });

    expect(updateLeague).toHaveBeenCalledWith('league-1', {
      name: 'Test League',
      isPublicDraftBoard: false,
      discordPicksAnnouncementEnabled: false,
    });
  });
});
