import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mount, flushPromises } from '@vue/test-utils';
import { createVuetify } from 'vuetify';
import ReportsView from '../../resources/js/views/ReportsView.vue';
import ReportRequestForm from '../../resources/js/components/reports/ReportRequestForm.vue';
import { REPORT_TYPES } from '../../resources/js/composables/mockReports';
import type { ApiReportJob, ListReportJobsResponse } from '../../resources/js/api/reports';

vi.mock('../../resources/js/api/reports', () => ({
    listReportJobs: vi.fn(),
    createReportJob: vi.fn(),
    retryReportJob: vi.fn(),
    cancelReportJob: vi.fn(),
    deleteReportJob: vi.fn(),
}));

const reportsApi = await import('../../resources/js/api/reports');

beforeEach(() => {
    vi.clearAllMocks();
    (reportsApi.listReportJobs as ReturnType<typeof vi.fn>).mockResolvedValue({
        jobs: [],
        total: 0,
        limit: 50,
        offset: 0,
        counts: { pending: 0, processing: 0, done: 0, failed: 0 },
        quota: { active: 0, max_active: 3 },
    } satisfies ListReportJobsResponse);
});

function makeApiJob(overrides: Partial<ApiReportJob> = {}): ApiReportJob {
    return {
        id: 1,
        type: 'deals_export',
        parameters: { format: 'csv', date_from: '2026-04-01', date_to: '2026-04-30' },
        status: 'done',
        file_path: 'reports/1/1.csv',
        file_size: 1024,
        generation_seconds: 2,
        error_message: null,
        created_at: new Date(Date.now() - 60_000).toISOString(),
        finished_at: new Date(Date.now() - 30_000).toISOString(),
        expires_at: new Date(Date.now() + 30 * 86_400_000).toISOString(),
        is_expired: false,
        download_url: 'http://localhost/api/reports/jobs/1/file?tenant=1&signature=fake',
        retry_count: 0,
        retry_max: 3,
        ...overrides,
    };
}

const mountView = async () => {
    const wrapper = mount(ReportsView, {
        global: { plugins: [createVuetify()] },
    });
    await flushPromises();
    return wrapper;
};

describe('ReportsView.vue (API integration)', () => {
    it('монтируется и содержит заголовок «Отчёты»', async () => {
        const wrapper = await mountView();
        expect(wrapper.find('h1').text()).toBe('Отчёты');
    });

    it('вызывает listReportJobs на mount', async () => {
        await mountView();
        expect(reportsApi.listReportJobs).toHaveBeenCalledTimes(1);
    });

    it('содержит 4 карточки типа отчёта', async () => {
        const wrapper = await mountView();
        const cards = wrapper.findAll('.tc-card');
        expect(cards).toHaveLength(REPORT_TYPES.length);
    });

    it('по умолчанию активна карточка «Сделки · детально»', async () => {
        const wrapper = await mountView();
        const dealsCard = wrapper.findAll('.tc-card').find((c) => c.text().includes('Сделки · детально'));
        expect(dealsCard?.classes()).toContain('active');
    });

    it('содержит 4 кнопки формата (CSV/XLSX/JSON/PDF)', async () => {
        const wrapper = await mountView();
        const text = wrapper.text();
        ['CSV', 'XLSX', 'JSON', 'PDF'].forEach((f) => expect(text).toContain(f));
    });

    it('quota-banner отражает API-данные (0 из 3)', async () => {
        const wrapper = await mountView();
        const text = wrapper.text();
        expect(text).toContain('0 из 3');
        expect(text).toContain('3 попыток retry');
        expect(text).toContain('7 дней');
    });

    it('пустой список → empty-state «Нет отчётов»', async () => {
        const wrapper = await mountView();
        expect(wrapper.text()).toContain('Нет отчётов');
    });

    it('done-job отображается с «Готов» + Скачать-кнопкой', async () => {
        (reportsApi.listReportJobs as ReturnType<typeof vi.fn>).mockResolvedValue({
            jobs: [makeApiJob({ status: 'done' })],
            total: 1,
            limit: 50,
            offset: 0,
            counts: { pending: 0, processing: 0, done: 1, failed: 0 },
            quota: { active: 0, max_active: 3 },
        } satisfies ListReportJobsResponse);
        const wrapper = await mountView();
        expect(wrapper.text()).toContain('Готов');
        expect(wrapper.find('[aria-label="Скачать"]').exists()).toBe(true);
    });

    it('failed-job отображается с «Ошибка» + Повторить-кнопкой при retry_count<3', async () => {
        (reportsApi.listReportJobs as ReturnType<typeof vi.fn>).mockResolvedValue({
            jobs: [makeApiJob({ status: 'failed', error_message: 'S3 timeout', retry_count: 1 })],
            total: 1,
            limit: 50,
            offset: 0,
            counts: { pending: 0, processing: 0, done: 0, failed: 1 },
            quota: { active: 0, max_active: 3 },
        } satisfies ListReportJobsResponse);
        const wrapper = await mountView();
        const text = wrapper.text();
        expect(text).toContain('Ошибка');
        expect(text).toContain('S3 timeout');
        expect(wrapper.find('[aria-label="Повторить"]').exists()).toBe(true);
    });

    it('failed-job с retry_count=3 НЕ показывает Повторить (max attempts)', async () => {
        (reportsApi.listReportJobs as ReturnType<typeof vi.fn>).mockResolvedValue({
            jobs: [makeApiJob({ status: 'failed', error_message: 'X', retry_count: 3 })],
            total: 1,
            limit: 50,
            offset: 0,
            counts: { pending: 0, processing: 0, done: 0, failed: 1 },
            quota: { active: 0, max_active: 3 },
        } satisfies ListReportJobsResponse);
        const wrapper = await mountView();
        expect(wrapper.find('[aria-label="Повторить"]').exists()).toBe(false);
    });

    it('queued-job (=pending) отображается с «В очереди» + Отменить', async () => {
        (reportsApi.listReportJobs as ReturnType<typeof vi.fn>).mockResolvedValue({
            jobs: [makeApiJob({ status: 'pending', file_path: null, file_size: null })],
            total: 1,
            limit: 50,
            offset: 0,
            counts: { pending: 1, processing: 0, done: 0, failed: 0 },
            quota: { active: 1, max_active: 3 },
        } satisfies ListReportJobsResponse);
        const wrapper = await mountView();
        expect(wrapper.text()).toContain('В очереди');
        expect(wrapper.find('[aria-label="Отменить"]').exists()).toBe(true);
    });

    it('Submit вызывает createReportJob с правильным payload + reload', async () => {
        (reportsApi.createReportJob as ReturnType<typeof vi.fn>).mockResolvedValue(makeApiJob());
        const wrapper = await mountView();
        await wrapper.find('[data-testid="submit-btn"]').trigger('click');
        await flushPromises();
        expect(reportsApi.createReportJob).toHaveBeenCalledOnce();
        const payload = (reportsApi.createReportJob as ReturnType<typeof vi.fn>).mock.calls[0][0];
        expect(payload.type).toBe('deals_export');
        expect(payload.format).toBe('csv');
        expect(payload.parameters).toHaveProperty('date_from');
        expect(payload.parameters).toHaveProperty('date_to');
        // Reload после submit (1 на mount + 1 после).
        expect(reportsApi.listReportJobs).toHaveBeenCalledTimes(2);
    });

    it('Submit при validation error → submit-error-alert', async () => {
        (reportsApi.createReportJob as ReturnType<typeof vi.fn>).mockRejectedValue({
            isAxiosError: true,
            response: {
                status: 422,
                data: { errors: { _quota: ['Лимит исчерпан'] } },
            },
        });
        const wrapper = await mountView();
        await wrapper.find('[data-testid="submit-btn"]').trigger('click');
        await flushPromises();
        expect(wrapper.find('[data-testid="submit-error-alert"]').exists()).toBe(true);
    });

    it('Submit-btn disabled когда квота заполнена (3/3)', async () => {
        (reportsApi.listReportJobs as ReturnType<typeof vi.fn>).mockResolvedValue({
            jobs: [],
            total: 0,
            limit: 50,
            offset: 0,
            counts: { pending: 3, processing: 0, done: 0, failed: 0 },
            quota: { active: 3, max_active: 3 },
        } satisfies ListReportJobsResponse);
        const wrapper = await mountView();
        const btn = wrapper.find('[data-testid="submit-btn"]');
        expect(btn.attributes('disabled')).toBeDefined();
    });

    it('Reset возвращает форму к defaults', async () => {
        const wrapper = await mountView();
        // Переключаем на managers
        const managersCard = wrapper.findAll('.tc-card').find((c) => c.text().includes('Менеджеры'));
        await managersCard!.trigger('click');
        await wrapper.vm.$nextTick();
        expect(managersCard!.classes()).toContain('active');

        // Reset
        await wrapper.find('[data-testid="reset-btn"]').trigger('click');
        await wrapper.vm.$nextTick();
        const dealsCard = wrapper.findAll('.tc-card').find((c) => c.text().includes('Сделки · детально'));
        expect(dealsCard!.classes()).toContain('active');
    });

    it('done-job: кнопка «Скачать» имеет href из download_url', async () => {
        (reportsApi.listReportJobs as ReturnType<typeof vi.fn>).mockResolvedValue({
            jobs: [makeApiJob({ status: 'done', download_url: 'http://localhost/dl?signature=xyz' })],
            total: 1,
            limit: 50,
            offset: 0,
            counts: { pending: 0, processing: 0, done: 1, failed: 0 },
            quota: { active: 0, max_active: 3 },
        } satisfies ListReportJobsResponse);
        const wrapper = await mountView();
        const dl = wrapper.find('[aria-label="Скачать"]');
        expect(dl.exists()).toBe(true);
        expect(dl.attributes('href')).toBe('http://localhost/dl?signature=xyz');
    });

    it('Reload-btn триггерит manual reload', async () => {
        const wrapper = await mountView();
        await wrapper.find('[data-testid="reload-btn"]').trigger('click');
        await flushPromises();
        expect(reportsApi.listReportJobs).toHaveBeenCalledTimes(2);
    });

    it('listReportJobs reject → fetch-error-alert', async () => {
        (reportsApi.listReportJobs as ReturnType<typeof vi.fn>).mockRejectedValue({
            isAxiosError: true,
            response: { status: 500, data: { message: 'Backend сломан' } },
        });
        const wrapper = await mountView();
        expect(wrapper.find('[data-testid="fetch-error-alert"]').exists()).toBe(true);
    });

    it('Retry-btn вызывает retryReportJob + reload', async () => {
        (reportsApi.listReportJobs as ReturnType<typeof vi.fn>).mockResolvedValue({
            jobs: [makeApiJob({ id: 42, status: 'failed', error_message: 'X', retry_count: 0 })],
            total: 1,
            limit: 50,
            offset: 0,
            counts: { pending: 0, processing: 0, done: 0, failed: 1 },
            quota: { active: 0, max_active: 3 },
        } satisfies ListReportJobsResponse);
        (reportsApi.retryReportJob as ReturnType<typeof vi.fn>).mockResolvedValue(makeApiJob({ id: 99 }));
        const wrapper = await mountView();
        await wrapper.find('[data-testid="retry-42"]').trigger('click');
        await flushPromises();
        expect(reportsApi.retryReportJob).toHaveBeenCalledWith(42);
        expect(reportsApi.listReportJobs).toHaveBeenCalledTimes(2);
    });

    it('Cancel-btn вызывает cancelReportJob + reload', async () => {
        (reportsApi.listReportJobs as ReturnType<typeof vi.fn>).mockResolvedValue({
            jobs: [makeApiJob({ id: 7, status: 'pending', file_path: null, file_size: null })],
            total: 1,
            limit: 50,
            offset: 0,
            counts: { pending: 1, processing: 0, done: 0, failed: 0 },
            quota: { active: 1, max_active: 3 },
        } satisfies ListReportJobsResponse);
        (reportsApi.cancelReportJob as ReturnType<typeof vi.fn>).mockResolvedValue(makeApiJob({ id: 7 }));
        const wrapper = await mountView();
        await wrapper.find('[data-testid="cancel-7"]').trigger('click');
        await flushPromises();
        expect(reportsApi.cancelReportJob).toHaveBeenCalledWith(7);
    });

    it('отмена задачи сбрасывает зелёный «отчёт создан» (submitSuccess)', async () => {
        (reportsApi.listReportJobs as ReturnType<typeof vi.fn>).mockResolvedValue({
            jobs: [makeApiJob({ id: 7, status: 'pending', file_path: null, file_size: null })],
            total: 1,
            limit: 50,
            offset: 0,
            counts: { pending: 1, processing: 0, done: 0, failed: 0 },
            quota: { active: 1, max_active: 3 },
        } satisfies ListReportJobsResponse);
        (reportsApi.createReportJob as ReturnType<typeof vi.fn>).mockResolvedValue(makeApiJob());
        (reportsApi.cancelReportJob as ReturnType<typeof vi.fn>).mockResolvedValue(makeApiJob({ id: 7 }));
        const wrapper = await mountView();

        // Создаём отчёт → зелёный «успех» загорается.
        await wrapper.find('[data-testid="submit-btn"]').trigger('click');
        await flushPromises();
        const form = wrapper.findComponent(ReportRequestForm);
        expect(form.props('submitSuccess')).toBe(true);

        // Отменяем задачу → зелёный «успех» должен погаснуть (не висеть после отмены).
        await wrapper.find('[data-testid="cancel-7"]').trigger('click');
        await flushPromises();
        expect(form.props('submitSuccess')).toBe(false);
    });

    it('Delete-btn открывает confirm-dialog + при подтверждении вызывает deleteReportJob', async () => {
        (reportsApi.listReportJobs as ReturnType<typeof vi.fn>).mockResolvedValue({
            jobs: [makeApiJob({ id: 5, status: 'done' })],
            total: 1,
            limit: 50,
            offset: 0,
            counts: { pending: 0, processing: 0, done: 1, failed: 0 },
            quota: { active: 0, max_active: 3 },
        } satisfies ListReportJobsResponse);
        (reportsApi.deleteReportJob as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
        const wrapper = await mountView();
        await wrapper.find('[data-testid="delete-5"]').trigger('click');
        await wrapper.vm.$nextTick();
        // Dialog имеет teleport-target вне wrapper'а; ищем глобально через document.
        const confirmBtn = document.querySelector('[data-testid="delete-confirm-btn"]') as HTMLElement | null;
        expect(confirmBtn).not.toBeNull();
        confirmBtn!.click();
        await flushPromises();
        expect(reportsApi.deleteReportJob).toHaveBeenCalledWith(5);
    });
});
