import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { computed, defineComponent, h, ref, watchEffect } from 'vue'
import type { Site } from '~/shared/types/sites'
import { useCurrentSite } from '~/modules/sites/composables/useCurrentSite'
import SiteSelector from '../SiteSelector.vue'

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

// Stubs des auto-imports Nuxt. SiteSelector.vue utilise useCurrentSite,
// useAuthStore, useI18n, watchEffect, computed sans import explicite
// (pattern Nuxt). En Vitest on les expose comme globals.
vi.stubGlobal('useCurrentSite', useCurrentSite)
vi.stubGlobal('useApi', () => ({ patch: mockPatch }))
vi.stubGlobal('useAuthStore', () => ({
    get user() {
        return mockAuthUser.value
    },
    setCurrentSite(site: Site | null) {
        if (mockAuthUser.value) {
            mockAuthUser.value.currentSite = site
        }
    },
}))
vi.stubGlobal('useI18n', () => ({ t: (key: string) => key }))
vi.stubGlobal('watchEffect', watchEffect)
vi.stubGlobal('computed', computed)
vi.stubGlobal('ref', ref)
// useSidebar et refreshNuxtData sont consommes par useCurrentSite apres
// un switch reussi — stubs minimaux pour eviter ReferenceError au mount.
vi.stubGlobal('useSidebar', () => ({ loadSidebar: vi.fn() }))
vi.stubGlobal('refreshNuxtData', vi.fn())

// Stub de MalioSiteSelector : on se contente de tracker les props recues
// et de re-emettre `change` quand on le simule via `trigger`. Evite de
// monter la vraie lib Malio (qui aurait besoin de tout Tailwind + twMerge).
const MalioSiteSelectorStub = defineComponent({
    name: 'MalioSiteSelector',
    props: {
        sites: { type: Array, required: true },
        modelValue: { type: String, default: undefined },
        groupClass: { type: String, default: '' },
        tileClass: { type: String, default: '' },
        labelClass: { type: String, default: '' },
    },
    emits: ['update:modelValue', 'change'],
    setup(props, { emit }) {
        return () => h('div', {
            'data-testid': 'malio-site-selector',
            'data-sites-count': String((props.sites as unknown[]).length),
            'data-active-id': String(props.modelValue ?? ''),
            'data-label-class': props.labelClass,
        }, [
            ...(props.sites as Array<{ id: string; name: string; color: string }>).map(site =>
                h('button', {
                    'data-testid': `tile-${site.id}`,
                    // Emet les deux events comme le vrai MalioSiteSelector
                    // (update:modelValue + change). Le wrapper n'ecoute que
                    // change aujourd'hui, mais tracker les deux grave la
                    // signature et prepare un eventuel v-model futur.
                    onClick: () => {
                        emit('update:modelValue', site.id)
                        emit('change', site)
                    },
                }, site.name),
            ),
        ])
    },
})

const SITE_A: Site = {
    id: 1,
    name: 'Chatellerault',
    street: '14 All.',
    complement: null,
    postalCode: '86100',
    city: 'Châtellerault',
    color: '#056CF2',
    fullAddress: '14 All.\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',
}

function mountSelector() {
    return mount(SiteSelector, {
        global: {
            stubs: { MalioSiteSelector: MalioSiteSelectorStub },
        },
    })
}

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

    it('rend un tile par site autorise', () => {
        const wrapper = mountSelector()
        const stub = wrapper.find('[data-testid="malio-site-selector"]')

        expect(stub.attributes('data-sites-count')).toBe('2')
    })

    it('marque le site courant via modelValue (string)', () => {
        const wrapper = mountSelector()
        const stub = wrapper.find('[data-testid="malio-site-selector"]')

        // Chatellerault id=1 => '1'
        expect(stub.attributes('data-active-id')).toBe('1')
    })

    it('passe labelClass="text-2xl" pour forcer 24px conforme Figma', () => {
        // Decision design : texte blanc par defaut Malio mais taille 24px
        // imposee par la maquette. Le reste des attributs text (white, bold,
        // uppercase, tracking-wide) provient du default Malio via twMerge.
        const wrapper = mountSelector()
        const stub = wrapper.find('[data-testid="malio-site-selector"]')

        expect(stub.attributes('data-label-class')).toBe('text-2xl')
    })

    it('clic sur un tile inactif declenche switchSite via PATCH /me/current-site', async () => {
        mockPatch.mockResolvedValueOnce({})
        const wrapper = mountSelector()

        await wrapper.find('[data-testid="tile-2"]').trigger('click')
        await flushPromises()

        expect(mockPatch).toHaveBeenCalledWith(
            '/me/current-site',
            { site: '/api/sites/2' },
            expect.anything(),
        )
    })

    it('clic sur le tile deja actif declenche un PATCH (resync cross-tab)', async () => {
        // Le court-circuit "si deja actif, ne rien faire" a ete supprime
        // pour couvrir le cas ou un autre onglet a modifie le site courant
        // cote serveur : un clic sur la tile localement "active" (etat
        // potentiellement stale) force une resync via PATCH. Le prix est un
        // PATCH superflu quand l'etat local est effectivement a jour.
        const wrapper = mountSelector()

        await wrapper.find('[data-testid="tile-1"]').trigger('click')
        await flushPromises()

        expect(mockPatch).toHaveBeenCalledWith(
            '/me/current-site',
            { site: '/api/sites/1' },
            expect.anything(),
        )
    })

    it('rollback visuel : sur erreur PATCH, data-active-id revient au site initial', async () => {
        // Scenario : admin clique sur Saint-Jean alors que Chatellerault est
        // actif, mais le serveur rejette (ex : 500). Apres rollback dans
        // useCurrentSite, le composant doit re-afficher Chatellerault actif.
        mockPatch.mockRejectedValueOnce(new Error('server down'))
        const wrapper = mountSelector()

        // Avant : Chatellerault (id=1) actif.
        expect(wrapper.find('[data-testid="malio-site-selector"]').attributes('data-active-id'))
            .toBe('1')

        await wrapper.find('[data-testid="tile-2"]').trigger('click')
        await flushPromises()

        // Apres rollback : Chatellerault (id=1) de nouveau actif.
        expect(wrapper.find('[data-testid="malio-site-selector"]').attributes('data-active-id'))
            .toBe('1')
        // Le store auth ne doit PAS avoir ete laisse avec SITE_B.
        expect(mockAuthUser.value?.currentSite).toEqual(SITE_A)
    })
})
