import { describe, it, expect, vi, beforeEach } from 'vitest'
import type { Site } from '~/shared/types/sites'
import { useCurrentSite } from '../useCurrentSite'

const mockPatch = vi.hoisted(() => vi.fn())
const mockAuthUser = vi.hoisted(() => ({
    value: null as { sites: Site[]; currentSite: Site | null } | null,
}))

// Stub des auto-imports Nuxt consommes par le composable.
vi.stubGlobal('useApi', () => ({ patch: mockPatch }))
vi.stubGlobal('useAuthStore', () => ({
    get user() {
        return mockAuthUser.value
    },
    // Mime l'action Pinia ajoutee au ticket 3 review (S6) : mute
    // user.currentSite si user present, no-op sinon.
    setCurrentSite(site: Site | null) {
        if (mockAuthUser.value) {
            mockAuthUser.value.currentSite = site
        }
    },
}))
vi.stubGlobal('useI18n', () => ({
    t: (key: string) => key,
}))
// useSidebar est consomme par useCurrentSite pour rafraichir la sidebar
// apres un switch reussi. Stub minimal retournant un loadSidebar no-op.
vi.stubGlobal('useSidebar', () => ({
    loadSidebar: vi.fn(),
}))
// refreshNuxtData est appele apres un switch pour invalider les donnees
// de page precedemment fetchees. Stub no-op pour les tests unitaires.
vi.stubGlobal('refreshNuxtData', vi.fn())

const SITE_A: Site = {
    id: 1,
    name: 'Chatellerault',
    street: '14 All. d\'Argenson',
    complement: null,
    postalCode: '86100',
    city: 'Châtellerault',
    color: '#056CF2',
    fullAddress: '14 All. d\'Argenson\n86100 Châtellerault',
}
const SITE_B: Site = {
    id: 2,
    name: 'Saint-Jean',
    street: 'Z i',
    complement: null,
    postalCode: '17400',
    city: 'Fontenet',
    color: '#F3CB00',
    fullAddress: 'Z i\n17400 Fontenet',
}

describe('useCurrentSite', () => {
    beforeEach(() => {
        mockPatch.mockReset()
        mockAuthUser.value = {
            sites: [SITE_A, SITE_B],
            currentSite: SITE_A,
        }
        const { resetCurrentSite } = useCurrentSite()
        resetCurrentSite()
    })

    it('syncFromAuth hydrate le state depuis le store auth', () => {
        const { syncFromAuth, currentSite, availableSites } = useCurrentSite()

        syncFromAuth()

        expect(currentSite.value).toEqual(SITE_A)
        expect(availableSites.value).toEqual([SITE_A, SITE_B])
    })

    it('syncFromAuth gere le cas user null (deconnecte)', () => {
        mockAuthUser.value = null
        const { syncFromAuth, currentSite, availableSites } = useCurrentSite()

        syncFromAuth()

        expect(currentSite.value).toBeNull()
        expect(availableSites.value).toEqual([])
    })

    it('switchSite met a jour currentSite localement AVANT la requete (optimistic)', async () => {
        mockPatch.mockImplementation(async () => {
            // Au moment du resolve, currentSite est deja basculé.
            const state = useCurrentSite()
            expect(state.currentSite.value).toEqual(SITE_B)
            return {}
        })

        const { syncFromAuth, switchSite, currentSite } = useCurrentSite()
        syncFromAuth()
        await switchSite(SITE_B)

        expect(currentSite.value).toEqual(SITE_B)
        expect(mockPatch).toHaveBeenCalledWith(
            '/me/current-site',
            { site: '/api/sites/2' },
            expect.objectContaining({ toastSuccessMessage: expect.any(String) }),
        )
    })

    it('switchSite propage le nouveau currentSite au store auth en cas de succes', async () => {
        mockPatch.mockResolvedValueOnce({})
        const { syncFromAuth, switchSite } = useCurrentSite()
        syncFromAuth()

        await switchSite(SITE_B)

        expect(mockAuthUser.value?.currentSite).toEqual(SITE_B)
    })

    it('switchSite rollback le currentSite local si la requete echoue', async () => {
        mockPatch.mockRejectedValueOnce(new Error('network'))
        const { syncFromAuth, switchSite, currentSite } = useCurrentSite()
        syncFromAuth()

        await expect(switchSite(SITE_B)).rejects.toThrow('network')

        expect(currentSite.value).toEqual(SITE_A)
    })

    it('switchSite ne propage pas au store auth en cas d\'echec', async () => {
        mockPatch.mockRejectedValueOnce(new Error('network'))
        const { syncFromAuth, switchSite } = useCurrentSite()
        syncFromAuth()

        await expect(switchSite(SITE_B)).rejects.toThrow()

        expect(mockAuthUser.value?.currentSite).toEqual(SITE_A)
    })

    it('switching est vrai pendant la requete et faux apres', async () => {
        let resolveRequest: (value: unknown) => void = () => {}
        mockPatch.mockImplementation(
            () => new Promise((resolve) => { resolveRequest = resolve }),
        )

        const { syncFromAuth, switchSite, switching } = useCurrentSite()
        syncFromAuth()

        const pending = switchSite(SITE_B)
        expect(switching.value).toBe(true)

        resolveRequest({})
        await pending

        expect(switching.value).toBe(false)
    })

    it('double switchSite concurrent : le second appel est un no-op silencieux', async () => {
        let resolveRequest: (value: unknown) => void = () => {}
        mockPatch.mockImplementation(
            () => new Promise((resolve) => { resolveRequest = resolve }),
        )

        const { syncFromAuth, switchSite } = useCurrentSite()
        syncFromAuth()

        const first = switchSite(SITE_B)
        await switchSite(SITE_A) // doit etre no-op (switching=true)

        // Le second appel ne declenche pas de PATCH additionnel.
        expect(mockPatch).toHaveBeenCalledTimes(1)

        resolveRequest({})
        await first
    })

    it('resetCurrentSite vide tout l\'etat singleton', () => {
        const { syncFromAuth, resetCurrentSite, currentSite, availableSites, switching } = useCurrentSite()
        syncFromAuth()
        expect(currentSite.value).not.toBeNull()

        resetCurrentSite()

        expect(currentSite.value).toBeNull()
        expect(availableSites.value).toEqual([])
        expect(switching.value).toBe(false)
    })

    it('capture useI18n/useApi/useAuthStore UNE FOIS au setup (garde anti-regression bug runtime)', async () => {
        // Historique : une premiere version du composable appelait useI18n()
        // dans `switchSite` plutot qu'au top du setup. Consequence en runtime :
        // l'appel depuis un event handler (click) hors contexte setup levait
        // "Must be called at the top of a setup function". Ce test grave le
        // contrat : useCurrentSite() DOIT capturer les 3 services a
        // l'initialisation, pas paresseusement.
        //
        // Verification : on remplace useI18n par un mock qui throw au 2e appel.
        // Si switchSite invoque useI18n() lui-meme, ce test cassera.
        let i18nCallCount = 0
        vi.stubGlobal('useI18n', () => {
            i18nCallCount++
            if (i18nCallCount > 1) {
                throw new Error('useI18n called more than once — regression bug runtime')
            }
            return { t: (key: string) => key }
        })

        mockPatch.mockResolvedValueOnce({})
        const { syncFromAuth, switchSite } = useCurrentSite()
        syncFromAuth()

        // Si switchSite appelait useI18n() en interne, ce call incrementerait
        // i18nCallCount a 2 et throw. La garde du test passe uniquement si
        // la capture a bien eu lieu au setup (i18nCallCount reste a 1).
        await switchSite(SITE_B)

        expect(i18nCallCount).toBe(1)

        // Restaure le stub par defaut pour les tests suivants.
        vi.stubGlobal('useI18n', () => ({ t: (key: string) => key }))
    })
})
