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

const { refreshMock, logoutMock } = vi.hoisted(() => ({
    refreshMock: vi.fn(),
    logoutMock: vi.fn(),
}));

vi.mock('@/stores/authStore', () => ({
    useAuthStore: { getState: () => ({ refresh: refreshMock, logout: logoutMock }) },
}));

import { api, ApiError } from '@/lib/api';

const replaceMock = vi.fn();

function jsonRes(body: unknown, ok = true, status = 200): Response {
    return { ok, status, json: () => Promise.resolve(body) } as unknown as Response;
}

function setToken(token: string) {
    localStorage.setItem('clinicpro-auth', JSON.stringify({ state: { token } }));
}

beforeEach(() => {
    localStorage.clear();
    refreshMock.mockReset();
    logoutMock.mockReset();
    replaceMock.mockReset();
    vi.stubGlobal('fetch', vi.fn());
    Object.defineProperty(window, 'location', {
        value: { replace: replaceMock },
        writable: true,
        configurable: true,
    });
});

describe('request — موفق', () => {
    it('بدنه JSON را برمی‌گرداند', async () => {
        (fetch as ReturnType<typeof vi.fn>).mockResolvedValue(
            jsonRes({ success: true, data: { id: 1 } }),
        );
        const out = await api.get<{ data: { id: number } }>('/api/v1/x');
        expect(out).toEqual({ success: true, data: { id: 1 } });
        expect(fetch).toHaveBeenCalledTimes(1);
    });

    it('وقتی توکن هست هدر Authorization می‌فرستد', async () => {
        setToken('tok-123');
        (fetch as ReturnType<typeof vi.fn>).mockResolvedValue(jsonRes({ ok: 1 }));
        await api.get('/api/v1/x');
        const headers = (fetch as ReturnType<typeof vi.fn>).mock.calls[0][1].headers;
        expect(headers['Authorization']).toBe('Bearer tok-123');
    });

    it('بدون توکن، هدر Authorization نمی‌فرستد', async () => {
        (fetch as ReturnType<typeof vi.fn>).mockResolvedValue(jsonRes({ ok: 1 }));
        await api.get('/api/v1/x');
        const headers = (fetch as ReturnType<typeof vi.fn>).mock.calls[0][1].headers;
        expect(headers['Authorization']).toBeUndefined();
        expect(headers['Content-Type']).toBe('application/json');
    });

    it('post بدنه را JSON.stringify و method=POST می‌فرستد', async () => {
        (fetch as ReturnType<typeof vi.fn>).mockResolvedValue(jsonRes({ ok: 1 }));
        await api.post('/api/v1/x', { a: 1 });
        const opts = (fetch as ReturnType<typeof vi.fn>).mock.calls[0][1];
        expect(opts.method).toBe('POST');
        expect(opts.body).toBe(JSON.stringify({ a: 1 }));
    });
});

describe('request — خطاهای غیر۴۰۱', () => {
    it('۴۲۲ → ApiError با code/message از errors[0]', async () => {
        (fetch as ReturnType<typeof vi.fn>).mockResolvedValue(
            jsonRes({ errors: [{ code: 'ERR_VALIDATION', message: 'نامعتبر' }] }, false, 422),
        );
        await expect(api.get('/api/v1/x')).rejects.toMatchObject({
            status: 422,
            code: 'ERR_VALIDATION',
            message: 'نامعتبر',
        });
    });

    it('بدنه بدون errors → ERR_UNKNOWN', async () => {
        (fetch as ReturnType<typeof vi.fn>).mockResolvedValue(jsonRes({}, false, 500));
        const err = (await api.get('/api/v1/x').catch((e) => e)) as ApiError;
        expect(err).toBeInstanceOf(ApiError);
        expect(err.code).toBe('ERR_UNKNOWN');
        expect(err.status).toBe(500);
    });
});

describe('request — جریان ۴۰۱', () => {
    it('refresh موفق → یک‌بار retry و موفقیت', async () => {
        refreshMock.mockResolvedValue('new-token');
        (fetch as ReturnType<typeof vi.fn>)
            .mockResolvedValueOnce(jsonRes({}, false, 401))
            .mockResolvedValueOnce(jsonRes({ success: true, data: 'ok' }));

        const out = await api.get('/api/v1/x');
        expect(out).toEqual({ success: true, data: 'ok' });
        expect(refreshMock).toHaveBeenCalledTimes(1);
        expect(fetch).toHaveBeenCalledTimes(2);
        expect(logoutMock).not.toHaveBeenCalled();
    });

    it('refresh ناموفق → logout + redirect + ApiError(401)', async () => {
        refreshMock.mockResolvedValue(null);
        (fetch as ReturnType<typeof vi.fn>).mockResolvedValue(jsonRes({}, false, 401));

        const err = (await api.get('/api/v1/x').catch((e) => e)) as ApiError;
        expect(err).toBeInstanceOf(ApiError);
        expect(err.status).toBe(401);
        expect(err.code).toBe('ERR_UNAUTHORIZED');
        expect(logoutMock).toHaveBeenCalledTimes(1);
        expect(replaceMock).toHaveBeenCalledWith('/admin/login');
    });

    it('de-dup: دو درخواست همزمان ۴۰۱ فقط یک‌بار refresh می‌زنند', async () => {
        refreshMock.mockImplementation(
            () => new Promise((r) => setTimeout(() => r('new-token'), 10)),
        );
        const f = fetch as ReturnType<typeof vi.fn>;
        f.mockImplementation(() => {
            const n = f.mock.calls.length;
            return Promise.resolve(n <= 2 ? jsonRes({}, false, 401) : jsonRes({ ok: 1 }));
        });

        await Promise.all([api.get('/api/v1/a'), api.get('/api/v1/b')]);
        expect(refreshMock).toHaveBeenCalledTimes(1);
        expect(fetch).toHaveBeenCalledTimes(4);
    });
});
