import { describe, it, expect, beforeEach, vi } from 'vitest';
import { createPinia, setActivePinia } from 'pinia';

// Мокаем api/auth до import'а auth-store.
vi.mock('../../resources/js/api/auth', () => ({
    login: vi.fn(),
    register: vi.fn(),
    me: vi.fn(),
    logout: vi.fn(),
    verifyTwoFactor: vi.fn(),
    forgotPassword: vi.fn(),
    resetPassword: vi.fn(),
    useRecoveryCode: vi.fn(),
}));

// Мокаем client (extractRateLimitRetry) — иначе axios.isAxiosError в jsdom возвращает false для plain Error'ов.
vi.mock('../../resources/js/api/client', () => ({
    extractRateLimitRetry: vi.fn(() => null),
    extractValidationErrors: vi.fn(() => null),
    extractErrorMessage: vi.fn(() => 'Произошла ошибка.'),
    apiClient: {},
    ensureCsrfCookie: vi.fn(),
}));

import * as authApi from '../../resources/js/api/auth';
import * as apiClient from '../../resources/js/api/client';
import { useAuthStore } from '../../resources/js/stores/auth';

describe('useAuthStore', () => {
    beforeEach(() => {
        setActivePinia(createPinia());
        vi.clearAllMocks();
    });

    it('initial state: user=null, isAuthenticated=false, requires2fa=false', () => {
        const auth = useAuthStore();
        expect(auth.user).toBeNull();
        expect(auth.isAuthenticated).toBe(false);
        expect(auth.requires2fa).toBe(false);
        expect(auth.loading).toBe(false);
    });

    it('login() при requires_2fa=true НЕ ставит user (pending state) — isAuthenticated остаётся false', async () => {
        vi.mocked(authApi.login).mockResolvedValue({
            user: {
                id: 1,
                email: 'test@example.ru',
                first_name: 'T',
                last_name: 'U',
                tenant_id: 1,
                totp_enabled: true,
                last_login_at: '2026-05-08T12:00:00Z',
            },
            requires_2fa: true,
        });

        const auth = useAuthStore();
        await auth.login({ email: 'test@example.ru', password: 'pass1234' });

        // user НЕ ставится при 2FA — backend ещё не сделал Auth::login.
        expect(auth.user).toBeNull();
        expect(auth.isAuthenticated).toBe(false);
        expect(auth.requires2fa).toBe(true);
    });

    it('login() при requires_2fa=false ставит user — isAuthenticated=true', async () => {
        vi.mocked(authApi.login).mockResolvedValue({
            user: {
                id: 2,
                email: 'no2fa@example.ru',
                first_name: 'N',
                last_name: 'U',
                tenant_id: 1,
                totp_enabled: false,
                last_login_at: null,
            },
            requires_2fa: false,
        });

        const auth = useAuthStore();
        await auth.login({ email: 'no2fa@example.ru', password: 'pass1234' });

        expect(auth.user?.email).toBe('no2fa@example.ru');
        expect(auth.isAuthenticated).toBe(true);
        expect(auth.requires2fa).toBe(false);
    });

    it('verifyTwoFactor() с правильным кодом ставит user + сбрасывает requires2fa', async () => {
        vi.mocked(authApi.verifyTwoFactor).mockResolvedValue({
            user: {
                id: 5,
                email: '2fa-done@example.ru',
                first_name: '2',
                last_name: 'F',
                tenant_id: 1,
                totp_enabled: true,
                last_login_at: '2026-05-08T12:00:00Z',
            },
            requires_2fa: false,
        });

        const auth = useAuthStore();
        // Эмулируем pending-state после login.
        auth.requires2fa = true;
        await auth.verifyTwoFactor('123456');

        expect(auth.user?.email).toBe('2fa-done@example.ru');
        expect(auth.isAuthenticated).toBe(true);
        expect(auth.requires2fa).toBe(false);
        expect(authApi.verifyTwoFactor).toHaveBeenCalledWith('123456');
    });

    it('verifyTwoFactor() при reject пробрасывает + НЕ ставит user', async () => {
        vi.mocked(authApi.verifyTwoFactor).mockRejectedValue(new Error('Invalid code'));

        const auth = useAuthStore();
        auth.requires2fa = true;
        await expect(auth.verifyTwoFactor('000000')).rejects.toThrow();

        expect(auth.user).toBeNull();
        expect(auth.requires2fa).toBe(true);
    });

    it('login() → reject пробрасывает ошибку и оставляет user=null', async () => {
        vi.mocked(authApi.login).mockRejectedValue(new Error('Invalid credentials'));

        const auth = useAuthStore();
        await expect(auth.login({ email: 'bad@example.ru', password: 'wrong' })).rejects.toThrow();

        expect(auth.user).toBeNull();
        expect(auth.isAuthenticated).toBe(false);
    });

    it('register() → success ставит pendingEmail, НЕ user (G1/SP3a)', async () => {
        vi.mocked(authApi.register).mockResolvedValue({
            status: 'pending_email_confirm',
            email: 'new@example.ru',
            expires_at: '2026-06-18T12:00:00+00:00',
            _dev_plain_code: '123456',
        });

        const auth = useAuthStore();
        await auth.register({
            email: 'new@example.ru',
            password: 'pass1234',
            accept_offer: true,
            accept_pdn: true,
            captcha_token: 'tok',
        });

        // Новый поток: сессия создаётся только при confirm-email, не при register.
        expect(auth.user).toBeNull();
        expect(auth.isAuthenticated).toBe(false);
        expect(auth.pendingEmail).toBe('new@example.ru');
        expect(auth.pendingDevCode).toBe('123456');
    });

    it('fetchMe() → success возвращает user + ставит state', async () => {
        vi.mocked(authApi.me).mockResolvedValue({
            id: 5,
            email: 'me@example.ru',
            first_name: 'M',
            last_name: 'E',
            tenant_id: 1,
            totp_enabled: false,
            last_login_at: null,
        });

        const auth = useAuthStore();
        const result = await auth.fetchMe();

        expect(result?.email).toBe('me@example.ru');
        expect(auth.user?.email).toBe('me@example.ru');
    });

    it('fetchMe() → 401 НЕ throw, возвращает null + чистит user', async () => {
        vi.mocked(authApi.me).mockRejectedValue(new Error('Unauthenticated'));

        const auth = useAuthStore();
        const result = await auth.fetchMe();

        expect(result).toBeNull();
        expect(auth.user).toBeNull();
    });

    it('login() при 429 кладёт retry_after в lockoutSeconds + пробрасывает ошибку', async () => {
        vi.mocked(authApi.login).mockRejectedValue({ response: { status: 429, data: { retry_after: 600 } } });
        vi.mocked(apiClient.extractRateLimitRetry).mockReturnValueOnce(600);

        const auth = useAuthStore();
        await expect(auth.login({ email: 'lock@example.ru', password: 'whatever-pass' })).rejects.toBeDefined();

        expect(auth.lockoutSeconds).toBe(600);
        expect(auth.user).toBeNull();
    });

    it('verifyTwoFactor() при 429 кладёт retry_after в lockoutSeconds', async () => {
        vi.mocked(authApi.verifyTwoFactor).mockRejectedValue({ response: { status: 429 } });
        vi.mocked(apiClient.extractRateLimitRetry).mockReturnValueOnce(900);

        const auth = useAuthStore();
        auth.requires2fa = true;
        await expect(auth.verifyTwoFactor('000000')).rejects.toBeDefined();

        expect(auth.lockoutSeconds).toBe(900);
    });

    it('успешный login() сбрасывает lockoutSeconds', async () => {
        vi.mocked(authApi.login).mockResolvedValue({
            user: {
                id: 1,
                email: 'ok@example.ru',
                first_name: 'O',
                last_name: 'K',
                tenant_id: 1,
                totp_enabled: false,
                last_login_at: null,
            },
            requires_2fa: false,
        });

        const auth = useAuthStore();
        // Эмулируем предыдущий lockout.
        auth.lockoutSeconds = 300;
        await auth.login({ email: 'ok@example.ru', password: 'pass1234' });

        expect(auth.lockoutSeconds).toBeNull();
    });

    it('requestPasswordReset() → success возвращает message без изменения user-state', async () => {
        vi.mocked(authApi.forgotPassword).mockResolvedValue({
            message: 'Если такой email зарегистрирован — ссылка отправлена.',
        });

        const auth = useAuthStore();
        const result = await auth.requestPasswordReset('forgot@example.ru');

        expect(result.message).toContain('ссылка отправлена');
        expect(authApi.forgotPassword).toHaveBeenCalledWith('forgot@example.ru');
        // user-state не меняется.
        expect(auth.user).toBeNull();
        expect(auth.isAuthenticated).toBe(false);
    });

    it('requestPasswordReset() при 429 кладёт retry_after в lockoutSeconds', async () => {
        vi.mocked(authApi.forgotPassword).mockRejectedValue({ response: { status: 429 } });
        vi.mocked(apiClient.extractRateLimitRetry).mockReturnValueOnce(900);

        const auth = useAuthStore();
        await expect(auth.requestPasswordReset('locked@example.ru')).rejects.toBeDefined();

        expect(auth.lockoutSeconds).toBe(900);
    });

    it('resetPassword() success возвращает message без auth-state', async () => {
        vi.mocked(authApi.resetPassword).mockResolvedValue({ message: 'Пароль успешно изменён.' });

        const auth = useAuthStore();
        const result = await auth.resetPassword({
            token: 'abc',
            email: 'a@b.ru',
            password: 'new-strong-pass-123',
            password_confirmation: 'new-strong-pass-123',
        });

        expect(result.message).toContain('успешно');
        expect(auth.user).toBeNull();
    });

    it('resetPassword() при 429 кладёт retry_after в lockoutSeconds', async () => {
        vi.mocked(authApi.resetPassword).mockRejectedValue({ response: { status: 429 } });
        vi.mocked(apiClient.extractRateLimitRetry).mockReturnValueOnce(900);

        const auth = useAuthStore();
        await expect(
            auth.resetPassword({
                token: 'bad',
                email: 'a@b.ru',
                password: 'new-strong-pass-123',
                password_confirmation: 'new-strong-pass-123',
            }),
        ).rejects.toBeDefined();

        expect(auth.lockoutSeconds).toBe(900);
    });

    it('useRecoveryCode() success ставит user + сбрасывает requires2fa', async () => {
        vi.mocked(authApi.useRecoveryCode).mockResolvedValue({
            user: {
                id: 7,
                email: 'recovery@example.ru',
                first_name: 'R',
                last_name: 'C',
                tenant_id: 1,
                totp_enabled: true,
                last_login_at: '2026-05-09T12:00:00Z',
            },
            requires_2fa: false,
            recovery_codes_remaining: 3,
        });

        const auth = useAuthStore();
        auth.requires2fa = true;
        const result = await auth.useRecoveryCode('ABCD-1234');

        expect(result.recovery_codes_remaining).toBe(3);
        expect(auth.user?.email).toBe('recovery@example.ru');
        expect(auth.requires2fa).toBe(false);
    });

    it('useRecoveryCode() при reject пробрасывает + НЕ ставит user', async () => {
        vi.mocked(authApi.useRecoveryCode).mockRejectedValue(new Error('Invalid code'));

        const auth = useAuthStore();
        auth.requires2fa = true;
        await expect(auth.useRecoveryCode('WRONG-1234')).rejects.toThrow();

        expect(auth.user).toBeNull();
        expect(auth.requires2fa).toBe(true);
    });

    it('logout() очищает user даже если API упал', async () => {
        vi.mocked(authApi.logout).mockRejectedValue(new Error('Network error'));

        const auth = useAuthStore();
        // Сначала задаём user.
        auth.user = {
            id: 1,
            email: 'x@example.ru',
            first_name: 'X',
            last_name: 'X',
            tenant_id: 1,
            totp_enabled: false,
            last_login_at: null,
        };

        await auth.logout();

        expect(auth.user).toBeNull();
        expect(auth.requires2fa).toBe(false);
    });
});
