import { describe, test, expect } from 'vitest';

import { ContestType } from '$lib/types/contest';

import {
  ABSProvider,
  ABC319OnwardsProvider,
  ABC212ToABC318Provider,
  ABC126ToABC211Provider,
  ABC042ToABC125Provider,
  ABC001ToABC041Provider,
  ARC104OnwardsProvider,
  ARC058ToARC103Provider,
  ARC001ToARC057Provider,
  AGC001OnwardsProvider,
  ABCLikeProvider,
  AWC0001To0099Provider,
  AWC0100Provider,
  AWC0101OnwardsProvider,
  ACLPracticeProvider,
  ACLBeginnerProvider,
  ACLProvider,
  EDPCProvider,
  TDPCProvider,
  NDPCProvider,
  FPS24Provider,
  JOIFirstQualRoundProvider,
  JOISecondQualRound2020OnwardsProvider,
  JOIQualRoundFrom2006To2019Provider,
  JOISemiFinalRoundProvider,
  Typical90Provider,
  TessokuBookForExamplesProvider,
  TessokuBookForPracticalsProvider,
  TessokuBookForChallengesProvider,
  MathAndAlgorithmProvider,
  AojIcpcPrelimProvider,
  AojIcpcRegionalProvider,
  JagPrelimProvider,
  prepareContestProviderPresets,
} from './contest_table_provider';
import { TESSOKU_SECTIONS } from '$features/tasks/types/contest-table/contest_table_provider';

import {
  ICPC_PRELIM_OLDEST_YEAR,
  ICPC_PRELIM_LATEST_YEAR,
  ICPC_REGIONAL_OLDEST_YEAR,
  ICPC_REGIONAL_LATEST_YEAR,
  JAG_PRELIM_OLDEST_YEAR,
  JAG_PRELIM_LATEST_YEAR,
} from './contest_table_provider_groups';

describe('prepareContestProviderPresets', () => {
  test('expects to create ABS preset correctly', () => {
    const group = prepareContestProviderPresets().ABS();

    expect(group.getGroupName()).toBe('AtCoder Beginners Selection');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'ABS',
      ariaLabel: 'Filter AtCoder Beginners Selection',
    });
    expect(group.getSize()).toBe(1);
    expect(group.getProvider(ContestType.ABS)).toBeInstanceOf(ABSProvider);
  });

  test('expects to create ABC319Onwards preset correctly', () => {
    const group = prepareContestProviderPresets().ABC319Onwards();

    expect(group.getGroupName()).toBe('ABC 319 Onwards');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'ABC 319 〜 ',
      ariaLabel: 'Filter contests from ABC 319 onwards',
    });
    expect(group.getSize()).toBe(1);
    expect(group.getProvider(ContestType.ABC)).toBeInstanceOf(ABC319OnwardsProvider);
  });

  test('expects to create fromABC212ToABC318 preset correctly', () => {
    const group = prepareContestProviderPresets().ABC212ToABC318();

    expect(group.getGroupName()).toBe('From ABC 212 to ABC 318');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'ABC 212 〜 318',
      ariaLabel: 'Filter contests from ABC 212 to ABC 318',
    });
    expect(group.getSize()).toBe(1);
    expect(group.getProvider(ContestType.ABC)).toBeInstanceOf(ABC212ToABC318Provider);
  });

  test('expects to create fromABC126ToABC211 preset correctly', () => {
    const group = prepareContestProviderPresets().ABC126ToABC211();

    expect(group.getGroupName()).toBe('From ABC 126 to ABC 211');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'ABC 126 〜 211',
      ariaLabel: 'Filter contests from ABC 126 to ABC 211',
    });
    expect(group.getSize()).toBe(1);
    expect(group.getProvider(ContestType.ABC)).toBeInstanceOf(ABC126ToABC211Provider);
  });

  test('expects to create fromABC042ToABC125 preset correctly', () => {
    const group = prepareContestProviderPresets().ABC042ToABC125();

    expect(group.getGroupName()).toBe('From ABC 042 to ABC 125');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'ABC 042 〜 125',
      ariaLabel: 'Filter contests from ABC 042 to ABC 125',
    });
    expect(group.getSize()).toBe(1);
    expect(group.getProvider(ContestType.ABC)).toBeInstanceOf(ABC042ToABC125Provider);
  });

  test('expects to create fromABC001ToABC041 preset correctly', () => {
    const group = prepareContestProviderPresets().ABC001ToABC041();

    expect(group.getGroupName()).toBe('From ABC 001 to ABC 041');
    expect(group.getMetadata()).toEqual({
      buttonLabel: '旧 ABC',
      ariaLabel: 'Filter contests from ABC 001 to ABC 041',
    });
    expect(group.getSize()).toBe(1);
    expect(group.getProvider(ContestType.ABC)).toBeInstanceOf(ABC001ToABC041Provider);
  });

  test('expects to create ARC104Onwards preset correctly', () => {
    const group = prepareContestProviderPresets().ARC104Onwards();

    expect(group.getGroupName()).toBe('ARC 104 Onwards');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'ARC 104 〜 ',
      ariaLabel: 'Filter contests from ARC 104 onwards',
    });
    expect(group.getSize()).toBe(1);
    expect(group.getProvider(ContestType.ARC)).toBeInstanceOf(ARC104OnwardsProvider);
  });

  test('expects to create ARC058ToARC103 preset correctly', () => {
    const group = prepareContestProviderPresets().ARC058ToARC103();

    expect(group.getGroupName()).toBe('ARC 058 To ARC 103');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'ARC 058 〜 103',
      ariaLabel: 'Filter contests from ARC 058 to ARC 103',
    });
    expect(group.getSize()).toBe(1);
    expect(group.getProvider(ContestType.ARC)).toBeInstanceOf(ARC058ToARC103Provider);
  });

  test('expects to create ARC001ToARC057 preset correctly', () => {
    const group = prepareContestProviderPresets().ARC001ToARC057();

    expect(group.getGroupName()).toBe('ARC 001 To ARC 057');
    expect(group.getMetadata()).toEqual({
      buttonLabel: '旧 ARC',
      ariaLabel: 'Filter contests from ARC 001 to ARC 057',
    });
    expect(group.getSize()).toBe(1);
    expect(group.getProvider(ContestType.ARC)).toBeInstanceOf(ARC001ToARC057Provider);
  });

  test('expects to create AGC001Onwards preset correctly', () => {
    const group = prepareContestProviderPresets().AGC001Onwards();

    expect(group.getGroupName()).toBe('AGC 001 Onwards');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'AGC 001 〜 ',
      ariaLabel: 'Filter contests from AGC 001 onwards',
    });
    expect(group.getSize()).toBe(1);
    expect(group.getProvider(ContestType.AGC)).toBeInstanceOf(AGC001OnwardsProvider);
  });

  test('expects to create ABCLike preset correctly', () => {
    const group = prepareContestProviderPresets().ABCLike();

    expect(group.getGroupName()).toBe('ABC-Like');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'ABC-Like',
      ariaLabel: 'Filter contests from ABC-Like',
    });
    expect(group.getSize()).toBe(1);
    expect(group.getProvider(ContestType.ABC_LIKE)).toBeInstanceOf(ABCLikeProvider);
  });

  test('expects to create AWC0001Onwards preset correctly', () => {
    const group = prepareContestProviderPresets().AWC0001Onwards();

    expect(group.getGroupName()).toBe('AWC 0001 Onwards');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'AWC 0001 〜 ',
      ariaLabel: 'Filter contests from AWC 0001 onwards',
    });
    expect(group.getSize()).toBe(3);
    expect(group.getProvider(ContestType.AWC, '0101To9999')).toBeInstanceOf(AWC0101OnwardsProvider);
    expect(group.getProvider(ContestType.AWC, '0100')).toBeInstanceOf(AWC0100Provider);
    expect(group.getProvider(ContestType.AWC)).toBeInstanceOf(AWC0001To0099Provider);
  });

  test('expects to create MathAndAlgorithm preset correctly', () => {
    const group = prepareContestProviderPresets().MathAndAlgorithm();

    expect(group.getGroupName()).toBe('アルゴリズムと数学');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'アルゴリズムと数学',
      ariaLabel: 'Filter Math and Algorithm',
    });
    expect(group.getSize()).toBe(1);
    expect(group.getProvider(ContestType.MATH_AND_ALGORITHM)).toBeInstanceOf(
      MathAndAlgorithmProvider,
    );
  });

  test('expects to create Typical90 preset correctly', () => {
    const group = prepareContestProviderPresets().Typical90();

    expect(group.getGroupName()).toBe('競プロ典型 90 問');
    expect(group.getMetadata()).toEqual({
      buttonLabel: '競プロ典型 90 問',
      ariaLabel: 'Filter Typical 90 Problems',
    });
    expect(group.getSize()).toBe(1);
    expect(group.getProvider(ContestType.TYPICAL90)).toBeInstanceOf(Typical90Provider);
  });

  test('expects to create TessokuBook preset correctly with 3 providers', () => {
    const group = prepareContestProviderPresets().TessokuBook();

    expect(group.getGroupName()).toBe('競技プログラミングの鉄則');
    expect(group.getMetadata()).toEqual({
      buttonLabel: '競技プログラミングの鉄則',
      ariaLabel: 'Filter Tessoku Book',
    });
    expect(group.getSize()).toBe(3);
    expect(group.getProvider(ContestType.TESSOKU_BOOK, TESSOKU_SECTIONS.EXAMPLES)).toBeInstanceOf(
      TessokuBookForExamplesProvider,
    );
    expect(group.getProvider(ContestType.TESSOKU_BOOK, TESSOKU_SECTIONS.PRACTICALS)).toBeInstanceOf(
      TessokuBookForPracticalsProvider,
    );
    expect(group.getProvider(ContestType.TESSOKU_BOOK, TESSOKU_SECTIONS.CHALLENGES)).toBeInstanceOf(
      TessokuBookForChallengesProvider,
    );
  });

  test('expects to create DPs preset correctly', () => {
    const group = prepareContestProviderPresets().dps();

    expect(group.getGroupName()).toBe('xDPC・FPS 24');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'xDPC・FPS 24',
      ariaLabel: 'xDPC and FPS 24 contests',
    });
    expect(group.getSize()).toBe(4);
    expect(group.getProvider(ContestType.EDPC)).toBeInstanceOf(EDPCProvider);
    expect(group.getProvider(ContestType.TDPC)).toBeInstanceOf(TDPCProvider);
    expect(group.getProvider(ContestType.NDPC)).toBeInstanceOf(NDPCProvider);
    expect(group.getProvider(ContestType.FPS_24)).toBeInstanceOf(FPS24Provider);
  });

  test('expects to create ACL preset correctly with 3 providers', () => {
    const group = prepareContestProviderPresets().Acl();

    expect(group.getGroupName()).toBe('AtCoder Library Contests');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'AtCoder Library (ACL)',
      ariaLabel: 'Filter ACL Contests',
    });
    expect(group.getSize()).toBe(3);
    expect(group.getProvider(ContestType.ACL_PRACTICE)).toBeInstanceOf(ACLPracticeProvider);
    expect(group.getProvider(ContestType.ABC_LIKE)).toBeInstanceOf(ACLBeginnerProvider);
    expect(group.getProvider(ContestType.ARC_LIKE)).toBeInstanceOf(ACLProvider);
  });

  test('expects to create JOI First Qual Round preset correctly with 1 provider', () => {
    const group = prepareContestProviderPresets().JOIFirstQualRound();

    expect(group.getGroupName()).toBe('JOI 一次予選');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'JOI 一次予選',
      ariaLabel: 'Filter JOI First Qualifying Round',
    });
    expect(group.getSize()).toBe(1);
    expect(group.getProvider(ContestType.JOI)).toBeInstanceOf(JOIFirstQualRoundProvider);
  });

  test('expects to create JOI Second Qual and Semi-Final Round preset correctly with 3 providers', () => {
    const group = prepareContestProviderPresets().JOISecondQualAndSemiFinalRound();

    expect(group.getGroupName()).toBe('JOI 二次予選・本選');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'JOI 二次予選・本選',
      ariaLabel: 'Filter JOI Second Qual and Semi-Final Round',
    });
    expect(group.getSize()).toBe(3);
    expect(group.getProvider(ContestType.JOI, '2020Onwards')).toBeInstanceOf(
      JOISecondQualRound2020OnwardsProvider,
    );
    expect(group.getProvider(ContestType.JOI, 'from2006To2019')).toBeInstanceOf(
      JOIQualRoundFrom2006To2019Provider,
    );
    expect(group.getProvider(ContestType.JOI, 'semiFinal')).toBeInstanceOf(
      JOISemiFinalRoundProvider,
    );
  });

  test('expects to create AojIcpcPrelim preset correctly', () => {
    const group = prepareContestProviderPresets().AojIcpcPrelim();

    expect(group.getGroupName()).toBe('ICPC 国内予選');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'ICPC 国内予選',
      ariaLabel: 'Filter ICPC Domestic Preliminary',
    });
    expect(group.getSize()).toBe(ICPC_PRELIM_LATEST_YEAR - ICPC_PRELIM_OLDEST_YEAR + 1); // 29
    expect(group.getProvider(ContestType.AOJ_ICPC, '2023')).toBeInstanceOf(AojIcpcPrelimProvider);
  });

  test('expects to create AojJagPrelim preset correctly', () => {
    const group = prepareContestProviderPresets().AojJagPrelim();

    expect(group.getGroupName()).toBe('JAG 模擬国内');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'JAG 模擬国内',
      ariaLabel: 'Filter JAG Domestic Preliminary',
    });
    // 2016 was held twice (A/B), adding one extra table beyond the year span.
    expect(group.getSize()).toBe(JAG_PRELIM_LATEST_YEAR - JAG_PRELIM_OLDEST_YEAR + 1 + 1); // 22 + 1 = 23
    expect(group.getProvider(ContestType.AOJ_JAG, '2023')).toBeInstanceOf(JagPrelimProvider);
    expect(group.getProvider(ContestType.AOJ_JAG, '2016A')).toBeInstanceOf(JagPrelimProvider);
    expect(group.getProvider(ContestType.AOJ_JAG, '2016B')).toBeInstanceOf(JagPrelimProvider);
  });

  test('expects to create AojIcpcRegional preset correctly', () => {
    const group = prepareContestProviderPresets().AojIcpcRegional();

    expect(group.getGroupName()).toBe('ICPC アジア地区');
    expect(group.getMetadata()).toEqual({
      buttonLabel: 'ICPC アジア地区',
      ariaLabel: 'Filter ICPC Asia Regional',
    });
    expect(group.getSize()).toBe(ICPC_REGIONAL_LATEST_YEAR - ICPC_REGIONAL_OLDEST_YEAR + 1); // 27
    expect(group.getProvider(ContestType.AOJ_ICPC, '2024')).toBeInstanceOf(AojIcpcRegionalProvider);
  });

  test('expects to verify all presets are functions', () => {
    const presets = prepareContestProviderPresets();

    expect(typeof presets.ABS).toBe('function');
    expect(typeof presets.ABC319Onwards).toBe('function');
    expect(typeof presets.ABC212ToABC318).toBe('function');
    expect(typeof presets.ABC126ToABC211).toBe('function');
    expect(typeof presets.ABC042ToABC125).toBe('function');
    expect(typeof presets.ARC104Onwards).toBe('function');
    expect(typeof presets.ARC058ToARC103).toBe('function');
    expect(typeof presets.AGC001Onwards).toBe('function');
    expect(typeof presets.ABCLike).toBe('function');
    expect(typeof presets.AWC0001Onwards).toBe('function');
    expect(typeof presets.ABC001ToABC041).toBe('function');
    expect(typeof presets.ARC001ToARC057).toBe('function');
    expect(typeof presets.Typical90).toBe('function');
    expect(typeof presets.TessokuBook).toBe('function');
    expect(typeof presets.MathAndAlgorithm).toBe('function');
    expect(typeof presets.dps).toBe('function');
    expect(typeof presets.Acl).toBe('function');
    expect(typeof presets.JOIFirstQualRound).toBe('function');
    expect(typeof presets.JOISecondQualAndSemiFinalRound).toBe('function');
    expect(typeof presets.AojIcpcPrelim).toBe('function');
    expect(typeof presets.AojIcpcRegional).toBe('function');
    expect(typeof presets.AojJagPrelim).toBe('function');
  });

  test('expects each preset to create independent instances', () => {
    const presets = prepareContestProviderPresets();
    const group1 = presets.ABS();
    const group2 = presets.ABS();

    expect(group1).not.toBe(group2);
    expect(group1.getGroupName()).toBe(group2.getGroupName());
    expect(group1.getProvider(ContestType.ABS)).not.toBe(group2.getProvider(ContestType.ABS));
  });
});
