import { describe, it, expect, vi, beforeEach } from 'vitest'
import { useFormErrors } from '../useFormErrors'

const mockToastError = vi.hoisted(() => vi.fn())
vi.stubGlobal('useToast', () => ({ error: mockToastError, success: vi.fn() }))
// useI18n stub : renvoie la cle telle quelle (pour asserter dessus).
vi.stubGlobal('useI18n', () => ({ t: (key: string) => key }))

/**
 * Tests du composable `useFormErrors` — pendant front de la regle « le back
 * renvoie toutes les violations 422 d'un coup » (ERP-101). Centralise l'etat
 * d'erreurs par champ (`Record<propertyPath, message>`) et la dispatch d'une
 * erreur API : 422 mappee inline, sinon toast de fallback.
 */
describe('useFormErrors', () => {
    beforeEach(() => {
        mockToastError.mockReset()
    })

    /** Fabrique une erreur ofetch avec status + payload. */
    function fetchError(status: number, data: unknown) {
        return { response: { status, _data: data } }
    }

    it('demarre sans erreur', () => {
        const { errors, hasErrors } = useFormErrors()
        expect(errors).toEqual({})
        expect(hasErrors.value).toBe(false)
    })

    it('setServerErrors mappe les violations par champ et retourne true', () => {
        const { errors, hasErrors, setServerErrors } = useFormErrors()
        const mapped = setServerErrors({
            violations: [
                { propertyPath: 'companyName', message: 'Obligatoire.' },
                { propertyPath: 'siren', message: 'Deja utilise.' },
            ],
        })
        expect(mapped).toBe(true)
        expect(errors).toEqual({ companyName: 'Obligatoire.', siren: 'Deja utilise.' })
        expect(hasErrors.value).toBe(true)
    })

    it('setServerErrors surcharge un message technique (erreur de type) par la cle i18n', () => {
        const { errors, setServerErrors } = useFormErrors()
        const mapped = setServerErrors({
            violations: [
                // Code Symfony Type::INVALID_TYPE_ERROR (date non parsable) : surcharge.
                { propertyPath: 'foundedAt', message: 'Cette valeur doit être de type DateTimeImmutable|null.', code: 'ba785a8c-82cb-4283-967c-3cf342181b40' },
                // Violation metier classique : message back conserve.
                { propertyPath: 'companyName', message: 'Obligatoire.', code: 'c1051bb4-d103-4f74-8988-acbcafc7fdc3' },
            ],
        })
        expect(mapped).toBe(true)
        // Stub i18n -> renvoie la cle telle quelle.
        expect(errors.foundedAt).toBe('errors.validation.invalidDate')
        expect(errors.companyName).toBe('Obligatoire.')
    })

    it('setServerErrors retourne false et ne touche rien sans violation', () => {
        const { errors, setServerErrors } = useFormErrors()
        expect(setServerErrors({})).toBe(false)
        expect(errors).toEqual({})
    })

    it('setError / clearError / clearErrors manipulent l\'etat finement', () => {
        const { errors, setError, clearError, clearErrors } = useFormErrors()
        setError('iban', 'IBAN invalide.')
        expect(errors.iban).toBe('IBAN invalide.')
        clearError('iban')
        expect(errors.iban).toBeUndefined()
        setError('a', 'x')
        setError('b', 'y')
        clearErrors()
        expect(errors).toEqual({})
    })

    it('handleApiError : 422 avec violations → mappe inline, pas de toast, retourne true', () => {
        const { errors, handleApiError } = useFormErrors()
        const handled = handleApiError(
            fetchError(422, { violations: [{ propertyPath: 'email', message: 'Invalide.' }] }),
        )
        expect(handled).toBe(true)
        expect(errors.email).toBe('Invalide.')
        expect(mockToastError).not.toHaveBeenCalled()
    })

    it('handleApiError : erreur non-422 → toast de fallback, retourne false', () => {
        const { errors, handleApiError } = useFormErrors()
        const handled = handleApiError(
            fetchError(500, { 'hydra:description': 'Erreur serveur.' }),
            { fallbackMessage: 'Oups.' },
        )
        expect(handled).toBe(false)
        expect(errors).toEqual({})
        expect(mockToastError).toHaveBeenCalledTimes(1)
        // Titre via i18n (cle renvoyee telle quelle par le stub).
        expect(mockToastError.mock.calls[0][0]).toMatchObject({ title: 'errors.title', message: 'Erreur serveur.' })
    })

    it('handleApiError : 422 sans violation mappable → toast de fallback, retourne false', () => {
        const { handleApiError } = useFormErrors()
        const handled = handleApiError(fetchError(422, { 'hydra:description': 'Donnees invalides.' }))
        expect(handled).toBe(false)
        expect(mockToastError).toHaveBeenCalledTimes(1)
    })
})
