import { describe, expect, beforeEach, test, vi } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { DateTime, Settings } from 'luxon'
import axios from 'axios'
import { useTranslator } from '../../stores/useTranslator'
import type { DictionaryConfig, DictionaryEntries, DictionaryResponse } from '../../interfaces'

const testDictionaryConfig: DictionaryConfig = {
    name: 'English',
    regional: 'English',
    authors: ['UserFrosting'],
    plural_rule: 1,
    dates: 'en-US'
}

const testDictionaryConfigFr: DictionaryConfig = {
    name: 'French',
    regional: 'Français',
    authors: ['Malou'],
    plural_rule: 1,
    dates: 'fr-FR'
}

const testDictionaryEntries: DictionaryEntries = {
    YES: 'Yes',
    NO: 'No',
    WELCOME_TO: 'Welcome to {{title}}!',
    'ERROR.@TRANSLATION': 'The Error',
    'ERROR.TITLE': "We've sensed a great disturbance in the Force.",
    USERNAME: 'Username',
    'COLOR.BLACK': 'black',
    'COLOR.RED': 'red',
    'COLOR.WHITE': 'white',
    'COLOR.0': 'colors',
    'COLOR.1': 'color',
    'COLOR.2': 'colors',
    'X_CARS.0': 'no cars',
    'X_CARS.1': 'a car',
    'X_CARS.2': '{{plural}} cars',
    'X_HUNGRY_CATS.@PLURAL': 'num',
    'X_HUNGRY_CATS.1': '{{num}} hungry cat',
    'X_HUNGRY_CATS.2': '{{num}} hungry cats',
    X_FOO: '{{plural}}x foos',
    'CAR.1': 'car',
    'CAR.2': 'cars',
    'CAR.GAS': 'gas',
    'CAR.EV.@TRANSLATION': 'electric',
    'CAR.EV.FULL': 'full electric',
    'CAR.FULL_MODEL': '{{make}} {{model}} {{year}}',
    'MY_CARS.@TRANSLATION': 'My cars',
    'MY_CARS.1': 'I have a {{type}} {{&CAR}}',
    'MY_CARS.2': 'I have {{plural}} {{type}} {{&CAR}}',
    TEST_LIMIT: 'Your test must be between {{min}} and {{max}} potatoes.',
    MIN: 'minimum',
    'X_RULES.0': 'no rules',
    'X_RULES.1': '{{plural}} rule', // No plural form for 2
    'X_BANANAS.0': 'no bananas', // No plural form for 1, 2, etc.
    'X_DOGS.5': 'five dogs',
    'X_DOGS.101': '101 Dalmatians',
    'X_DOGS.1000': 'An island of dogs'
}

const testDictionary: DictionaryResponse = {
    identifier: 'en_US',
    config: testDictionaryConfig,
    dictionary: testDictionaryEntries
}

const testDictionaryFr: DictionaryResponse = {
    identifier: 'fr_FR',
    config: testDictionaryConfigFr,
    dictionary: testDictionaryEntries
}

describe('API Tests', async () => {
    test('API should be saved', async () => {
        // Arrange
        setActivePinia(createPinia())
        const translator = useTranslator()
        const { translate, load } = translator
        const response = { data: testDictionary }
        vi.spyOn(axios, 'get').mockResolvedValue(response as any)

        // Assert initial state
        expect(translator.identifier).toBe('')
        expect(translator.config).toEqual({
            name: '',
            regional: '',
            authors: [],
            plural_rule: 0,
            dates: ''
        })

        // Act
        await load()

        // Assert config data
        expect(axios.get).toHaveBeenCalledWith('/api/dictionary')
        expect(translator.config).toEqual(testDictionaryConfig)
        expect(translator.identifier).toBe('en_US')

        // Assert basic translate method
        expect(translate('YES')).toBe('Yes')
        expect(translate('NO')).toBe('No')
        expect(translate('WELCOME_TO')).toBe('Welcome to {{title}}!')
    })
})

describe('Translator Tests', () => {
    beforeEach(async () => {
        // Arrange
        setActivePinia(createPinia())
        const translator = useTranslator()
        const { load } = translator
        const response = { data: testDictionary }
        vi.spyOn(axios, 'get').mockResolvedValue(response as any)

        // Act
        await load()
    })

    test('Should handle basic translation', () => {
        const { translate } = useTranslator()
        expect(translate('USERNAME')).toBe('Username')
    })

    test('Should handle key not existing', () => {
        const { translate } = useTranslator()
        expect(translate('NOT_EXIST')).toBe('NOT_EXIST')
        expect(translate('NOT EXIST')).toBe('NOT EXIST')
    })

    test('Should handle @TRANSLATION', () => {
        const { translate } = useTranslator()
        expect(translate('ERROR')).toBe('The Error')
        expect(translate('ERROR.TITLE')).toBe("We've sensed a great disturbance in the Force.")
    })

    test('Should handle placeholders', () => {
        const { translate } = useTranslator()
        expect(translate('WELCOME_TO')).toBe('Welcome to {{title}}!') // Placeholder not replaced
        expect(translate('WELCOME_TO', { title: 'UserFrosting' })).toBe('Welcome to UserFrosting!')
        expect(translate('WELCOME_TO', { bar: 'UserFrosting' })).toBe('Welcome to {{title}}!') // Wrong key
        expect(translate('WELCOME_TO', 'UserFrosting')).toBe('Welcome to {{title}}!') // Key not provided
        expect(translate('WELCOME_TO', { title: 'UserFrosting', foo: 'bar' })).toBe(
            'Welcome to UserFrosting!'
        ) // Extra key not used
    })

    test('Should handle basic plurals', () => {
        const { translate } = useTranslator()
        expect(translate('X_CARS', 0)).toBe('no cars')
        expect(translate('X_CARS', 1)).toBe('a car')
        expect(translate('X_CARS', 2)).toBe('2 cars')
        expect(translate('X_CARS', 5)).toBe('5 cars')
    })

    test('Should handle pluralization with custom plural key', () => {
        const { translate } = useTranslator()
        expect(translate('X_HUNGRY_CATS', { num: 0 })).toBe('0 hungry cats')
        expect(translate('X_HUNGRY_CATS', { num: 1 })).toBe('1 hungry cat')
        expect(translate('X_HUNGRY_CATS', { num: 2 })).toBe('2 hungry cats')
        expect(translate('X_HUNGRY_CATS', { num: 5 })).toBe('5 hungry cats')

        // Custom key can also be omitted in the placeholder if it's the only
        // placeholder even with custom plural key
        expect(translate('X_HUNGRY_CATS', 5)).toBe('5 hungry cats')

        // Test missing pluralization and placeholder (default to 1)
        expect(translate('X_HUNGRY_CATS')).toBe('1 hungry cat')
    })

    test('Should handle plurals default, when no placeholder', () => {
        const { translate } = useTranslator()
        expect(translate('X_CARS')).toBe('a car')
    })

    test('Should handle Key With No Plural', () => {
        const { translate } = useTranslator()
        expect(translate('USERNAME', 123)).toBe('Username') // USERNAME has no placeholders
        expect(translate('X_FOO')).toBe('{{plural}}x foos') // 'X_FOO' doesn't have children, so it's not treated as a "pluralize-able" string
        expect(translate('X_FOO', { plural: 1 })).toBe('1x foos') // Replace {{plural}} with 1
        expect(translate('X_FOO', 1)).toBe('1x foos') // Replace {{plural}} with 1, without specifying the key
        expect(translate('X_FOO', 123)).toBe('123x foos') // Replace {{plural}} with 123
    })

    test('Should handle plurals for different plural rules', () => {
        const { translate } = useTranslator()

        // English plural rule is 1
        expect(translate('COLOR', 0)).toBe('colors')
        expect(translate('COLOR', 1)).toBe('color')
        expect(translate('COLOR', 2)).toBe('colors')
        expect(translate('COLOR', 3)).toBe('colors')

        // Same as above, but with a custom plural key (french)
        // Note "0" is plural (colors) in english, singular (couleur) in french !
        // TODO : Implement plurals & load custom locale
        // expect(translate('COLOR', 0)).toBe('colors')
        // expect(translate('COLOR', 1)).toBe('color')
        // expect(translate('COLOR', 2)).toBe('colors')
        // expect(translate('COLOR', 3)).toBe('colors')
    })

    test('Should handle a simple replacement when the key is not defined in dictionary', () => {
        const { translate } = useTranslator()
        expect(translate('You are {{status}}', { status: 'dumb' })).toBe('You are dumb')
    })

    test('Should handle @TRANSLATION when the key have plural rules', () => {
        const { translate } = useTranslator()
        expect(translate('MY_CARS')).toBe('My cars') // @TRANSLATION is used, not the 1 rule
        expect(translate('MY_CARS', 1)).toBe('I have a {{type}} car')
        expect(translate('MY_CARS', 2)).toBe('I have 2 {{type}} cars')
    })

    test('Should handle complex placeholders', () => {
        const { translate } = useTranslator()
        expect(translate('MY_CARS', { type: 'car' })).toBe('My cars')
        expect(translate('MY_CARS', { type: 'gaz', plural: 1 })).toBe('I have a gaz car')
        expect(translate('MY_CARS', { type: 'gaz', plural: 2 })).toBe('I have 2 gaz cars')
        expect(translate('MY_CARS', { type: '&CAR.GAS', plural: 2 })).toBe('I have 2 gas cars')
        expect(translate('MY_CARS', { type: '&CAR.EV', plural: 2 })).toBe('I have 2 electric cars')
        expect(translate('MY_CARS', { type: '&CAR.EV.FULL', plural: 1 })).toBe(
            'I have a full electric car'
        )
        expect(
            translate('MY_CARS', {
                type: '&CAR.FULL_MODEL',
                plural: 5,
                make: 'Toyota',
                model: 'Camry',
                year: 2022
            })
        ).toBe('I have 5 Toyota Camry 2022 cars')
    })

    // Test basic placeholder replacement using int as placeholder value (So they don't try to translate "min" and "max")
    // We don't want to end up with "Your test must be between _minimum_ and 200 potatoes"
    test('Should handle placeholder not overwritten by other key', () => {
        const { translate } = useTranslator()
        expect(translate('TEST_LIMIT', { min: 4, max: 200 })).toBe(
            'Your test must be between 4 and 200 potatoes.'
        )
    })

    // 2 will return singular as the plural is not defined
    test('Should handle pluralization with no rules', () => {
        const { translate } = useTranslator()
        expect(translate('X_RULES', 0)).toBe('no rules')
        expect(translate('X_RULES', 1)).toBe('1 rule')
        expect(translate('X_RULES', 2)).toBe('2 rule')

        // X_BANANAS
        expect(translate('X_BANANAS', 0)).toBe('no bananas')
        expect(translate('X_BANANAS', 1)).toBe('no bananas')
        expect(translate('X_BANANAS', 2)).toBe('no bananas')
        expect(translate('X_BANANAS', 5)).toBe('no bananas')
    })

    // The keys are int, but don't follow the rules. It will fallback to the literal key
    test("Should handle plurals who doesn't follow the rules", () => {
        const { translate } = useTranslator()
        expect(translate('X_DOGS')).toBe('X_DOGS')
        expect(translate('X_DOGS', 0)).toBe('X_DOGS')
        expect(translate('X_DOGS', 1)).toBe('X_DOGS')
        expect(translate('X_DOGS', 2)).toBe('X_DOGS') // No plural rules found
        expect(translate('X_DOGS', 5)).toBe('five dogs') // This one is hardcoded and will fallback as normal string key
        expect(translate('X_DOGS', 101)).toBe('101 Dalmatians') // Same here
        expect(translate('X_DOGS', 102)).toBe('X_DOGS') // This one is not hardcoded
        expect(translate('X_DOGS', 1000)).toBe('An island of dogs') // Still fallback, if the key is a string representing and INT
    })
})

describe('Date Tests', async () => {
    test('translateDate should return the correct values in English', async () => {
        // Arrange
        setActivePinia(createPinia())
        const translator = useTranslator()
        const { translateDate, load, getDateTime } = translator
        const response = { data: testDictionary }
        vi.spyOn(axios, 'get').mockResolvedValue(response as any)
        await load()

        // Assert basic translate method
        // Force the default timezone, so the test is consistent regardless of
        // the timezone of the runner
        Settings.defaultZone = 'America/New_York'
        expect(translateDate('2025-02-02T14:42:12.000000Z')).toBe('Sun, Feb 2, 2025, 9:42 AM')
        expect(translateDate('2025-02-02T14:42:12.000000Z', 'DDD')).toBe('February 2, 2025')
        expect(translateDate('2025-02-02T14:42:12.000000Z', DateTime.DATETIME_MED)).toBe(
            'Feb 2, 2025, 9:42 AM'
        )

        // Test get the Datetime object
        expect(getDateTime('2025-02-02T14:42:12.000000Z').monthLong).toBe('February')
    })

    test('translateDate should return the correct values in French', async () => {
        // Arrange
        setActivePinia(createPinia())
        const translator = useTranslator()
        const { translateDate, load, getDateTime } = translator
        const response = { data: testDictionaryFr }
        vi.spyOn(axios, 'get').mockResolvedValue(response as any)
        await load()

        // Assert basic translate method
        // Force the default timezone, so the test is consistent regardless of
        // the timezone of the runner
        Settings.defaultZone = 'America/New_York'
        expect(translateDate('2025-02-02T14:42:12.000000Z')).toBe('dim. 2 févr. 2025, 09:42')
        expect(translateDate('2025-02-02T14:42:12.000000Z', 'DDD')).toBe('2 février 2025')
        expect(translateDate('2025-02-02T14:42:12.000000Z', DateTime.DATETIME_MED)).toBe(
            '2 févr. 2025, 09:42'
        )

        // Test get the Datetime object
        expect(getDateTime('2025-02-02T14:42:12.000000Z').monthLong).toBe('février')
    })
})

describe('Plural Form Tests', () => {
    beforeEach(async () => {
        // Arrange
        setActivePinia(createPinia())
        const translator = useTranslator()
        const { load } = translator
        const response = { data: testDictionary }
        vi.spyOn(axios, 'get').mockResolvedValue(response as any)

        // Act
        await load()
    })

    test('Should return correct plural form for rule 1', () => {
        const { getPluralForm } = useTranslator()
        expect(getPluralForm(0, 1)).toBe(2)
        expect(getPluralForm(1, 1)).toBe(1)
        expect(getPluralForm(2, 1)).toBe(2)
        expect(getPluralForm(20, 1)).toBe(2)
    })

    test('Should return correct plural form for rule 2', () => {
        const { getPluralForm } = useTranslator()
        expect(getPluralForm(0, 2)).toBe(1) // French = zero is singular
        expect(getPluralForm(1, 2)).toBe(1)
        expect(getPluralForm(2, 2)).toBe(2)
        expect(getPluralForm(20, 2)).toBe(2)
    })

    test('Should throw error for invalid rule', () => {
        const { getPluralForm } = useTranslator()
        expect(() => getPluralForm(1, 16)).toThrow('The rule number 16 must be between 0 and 15')
    })

    test('Should use default rule if forceRule is not provided', () => {
        const { getPluralForm } = useTranslator()
        expect(getPluralForm(0)).toBe(2)
        expect(getPluralForm(1)).toBe(1)
        expect(getPluralForm(2)).toBe(2)
    })

    test('Should used the specified rule if forceRule is provided', () => {
        const { getPluralForm } = useTranslator()
        expect(getPluralForm(0, 1)).toBe(2) // English
        expect(getPluralForm(0, 2)).toBe(1) // French
        expect(getPluralForm(5, 1)).toBe(2) // English
        expect(getPluralForm(5, 8)).toBe(3) // Slavic
    })
})

// TODO : Test each plural rules
