import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mount, flushPromises } from '@vue/test-utils';
import { createVuetify } from 'vuetify';
import AdminBillingView from '../../resources/js/views/admin/AdminBillingView.vue';
import type { ApiAdminBillingTenant } from '../../resources/js/api/admin';

/**
 * Создаёт объект, который проходит `axios.isAxiosError()` (проверяет флаг `isAxiosError: true`),
 * с нужным `response.data.message`.
 */
function makeAxiosError(message: string, status = 422): unknown {
    return Object.assign(new Error(message), {
        isAxiosError: true,
        response: { status, data: { message } },
    });
}

vi.mock('../../resources/js/api/admin', async (importOriginal) => {
    const orig = await importOriginal<typeof import('../../resources/js/api/admin')>();
    return {
        ...orig,
        listAdminBilling: vi.fn(),
        listAdminTariffPlans: vi.fn(),
        updateTenantStatus: vi.fn(),
        refundTenant: vi.fn(),
        changeTenantTariff: vi.fn(),
    };
});

const adminApi = await import('../../resources/js/api/admin');

beforeEach(() => {
    vi.clearAllMocks();
});

function makeApiBillingTenant(overrides: Partial<ApiAdminBillingTenant> = {}): ApiAdminBillingTenant {
    return {
        id: 42,
        subdomain: 'acme',
        organization_name: 'Acme ООО',
        contact_email: 'admin@acme.io',
        status: 'active',
        balance_rub: '5000.00',
        tariff_id: 1,
        tariff_name: 'Команда',
        mrr_rub: '990.00',
        monthly_topups_rub: '10000.00',
        monthly_charges_rub: '8000.00',
        last_payment_at: '2026-05-01T10:00:00Z',
        chargeback_unrecovered_rub: '0.00',
        ...overrides,
    };
}

function makeBillingResponse(tenants: ApiAdminBillingTenant[]) {
    return {
        tenants,
        summary: {
            total_mrr_rub: '990.00',
            monthly_revenue_rub: '10000.00',
            overdue_count: 0,
            refunds_count_30d: 0,
        },
    };
}

const mountView = () =>
    mount(AdminBillingView, {
        global: { plugins: [createVuetify()] },
    });

describe('AdminBillingView — row-actions menu (G4)', () => {
    it('каждая строка содержит кнопку действий [data-testid="row-actions-{id}"]', async () => {
        vi.mocked(adminApi.listAdminBilling).mockResolvedValueOnce(
            makeBillingResponse([makeApiBillingTenant({ id: 42 })]),
        );

        const wrapper = mountView();
        await flushPromises();

        expect(wrapper.find('[data-testid="row-actions-42"]').exists()).toBe(true);
    });

    it('openAction("status", row) устанавливает actionDialog="status" и actionRow', async () => {
        vi.mocked(adminApi.listAdminBilling).mockResolvedValueOnce(
            makeBillingResponse([makeApiBillingTenant({ id: 42 })]),
        );

        const wrapper = mountView();
        await flushPromises();

        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const vm = wrapper.vm as any;
        const row = vm.rowsState[0];

        await vm.openAction('status', row);
        await wrapper.vm.$nextTick();

        expect(vm.actionDialog).toBe('status');
        expect(vm.actionRow).toBe(row);
        expect(vm.actionReason).toBe('');
        expect(vm.actionError).toBe('');
    });

    it('confirmAction() вызывает updateTenantStatus и затем loadBilling', async () => {
        vi.mocked(adminApi.listAdminBilling).mockResolvedValue(
            makeBillingResponse([makeApiBillingTenant({ id: 42, status: 'active' })]),
        );
        vi.mocked(adminApi.updateTenantStatus).mockResolvedValueOnce({ id: 42, status: 'suspended' });

        const wrapper = mountView();
        await flushPromises();

        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const vm = wrapper.vm as any;
        const row = vm.rowsState[0];

        await vm.openAction('status', row);
        vm.actionReason = 'Основание для блокировки тенанта';

        await vm.confirmAction();
        await flushPromises();

        expect(adminApi.updateTenantStatus).toHaveBeenCalledWith(42, 'suspended', 'Основание для блокировки тенанта');
        expect(adminApi.listAdminBilling).toHaveBeenCalledTimes(2); // mount + after action
        expect(vm.actionDialog).toBeNull();
    });

    it('confirmAction() вызывает refundTenant с суммой и причиной', async () => {
        vi.mocked(adminApi.listAdminBilling).mockResolvedValue(makeBillingResponse([makeApiBillingTenant({ id: 42 })]));
        vi.mocked(adminApi.refundTenant).mockResolvedValueOnce({
            id: 42,
            balance_rub: '4500.00',
            transaction_id: 999,
        });

        const wrapper = mountView();
        await flushPromises();

        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const vm = wrapper.vm as any;
        const row = vm.rowsState[0];

        await vm.openAction('refund', row);
        vm.actionAmount = 500;
        vm.actionReason = 'Возврат по заявке клиента';

        await vm.confirmAction();
        await flushPromises();

        expect(adminApi.refundTenant).toHaveBeenCalledWith(42, 500, 'Возврат по заявке клиента');
        expect(vm.actionDialog).toBeNull();
    });

    it('openAction("tariff") вызывает listAdminTariffPlans и заполняет tariffPlans', async () => {
        vi.mocked(adminApi.listAdminBilling).mockResolvedValueOnce(
            makeBillingResponse([makeApiBillingTenant({ id: 42 })]),
        );
        vi.mocked(adminApi.listAdminTariffPlans).mockResolvedValueOnce([
            { id: 1, name: 'Старт', price_monthly: '490.00' },
            { id: 2, name: 'Команда', price_monthly: '990.00' },
        ]);

        const wrapper = mountView();
        await flushPromises();

        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const vm = wrapper.vm as any;
        const row = vm.rowsState[0];

        await vm.openAction('tariff', row);
        await flushPromises();

        expect(adminApi.listAdminTariffPlans).toHaveBeenCalledTimes(1);
        expect(vm.tariffPlans).toHaveLength(2);
        expect(vm.tariffPlans[0].name).toBe('Старт');
    });

    it('confirmAction("tariff") вызывает changeTenantTariff', async () => {
        vi.mocked(adminApi.listAdminBilling).mockResolvedValue(makeBillingResponse([makeApiBillingTenant({ id: 42 })]));
        vi.mocked(adminApi.listAdminTariffPlans).mockResolvedValueOnce([
            { id: 2, name: 'Команда', price_monthly: '990.00' },
        ]);
        vi.mocked(adminApi.changeTenantTariff).mockResolvedValueOnce({
            id: 42,
            tariff_id: 2,
            tariff_name: 'Команда',
        });

        const wrapper = mountView();
        await flushPromises();

        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const vm = wrapper.vm as any;
        const row = vm.rowsState[0];

        await vm.openAction('tariff', row);
        await flushPromises();

        vm.actionTariffId = 2;
        vm.actionReason = 'Смена тарифа по просьбе клиента';

        await vm.confirmAction();
        await flushPromises();

        expect(adminApi.changeTenantTariff).toHaveBeenCalledWith(42, 2, 'Смена тарифа по просьбе клиента');
        expect(vm.actionDialog).toBeNull();
    });

    it('API-ошибка в confirmAction — actionError содержит backend-сообщение, диалог остаётся открытым', async () => {
        vi.mocked(adminApi.listAdminBilling).mockResolvedValueOnce(
            makeBillingResponse([makeApiBillingTenant({ id: 42 })]),
        );
        vi.mocked(adminApi.updateTenantStatus).mockRejectedValueOnce(
            makeAxiosError('Нельзя заблокировать — есть долги'),
        );

        const wrapper = mountView();
        await flushPromises();

        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const vm = wrapper.vm as any;
        const row = vm.rowsState[0];

        await vm.openAction('status', row);
        vm.actionReason = 'Причина блокировки тенанта';

        await vm.confirmAction();
        await flushPromises();

        // Dialog stays open, exact backend message surfaces, loading cleared
        expect(vm.actionDialog).toBe('status');
        expect(vm.actionError).toBe('Нельзя заблокировать — есть долги');
        expect(vm.actionLoading).toBe(false);
    });

    it('openAction("tariff") — ошибка загрузки тарифов устанавливает actionError, диалог остаётся открытым', async () => {
        vi.mocked(adminApi.listAdminBilling).mockResolvedValueOnce(
            makeBillingResponse([makeApiBillingTenant({ id: 42 })]),
        );
        vi.mocked(adminApi.listAdminTariffPlans).mockRejectedValueOnce(
            makeAxiosError('Тарифы временно недоступны', 503),
        );

        const wrapper = mountView();
        await flushPromises();

        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const vm = wrapper.vm as any;
        const row = vm.rowsState[0];

        await vm.openAction('tariff', row);
        await flushPromises();

        expect(vm.actionDialog).toBe('tariff');
        expect(vm.actionError).toBe('Тарифы временно недоступны');
    });

    it('возврат суммы больше баланса → actionError, refundTenant не вызывается', async () => {
        vi.mocked(adminApi.listAdminBilling).mockResolvedValueOnce(
            makeBillingResponse([makeApiBillingTenant({ id: 42, balance_rub: '1000.00' })]),
        );

        const wrapper = mountView();
        await flushPromises();

        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const vm = wrapper.vm as any;
        const row = vm.rowsState[0]; // balance_rub = 1000

        await vm.openAction('refund', row);
        vm.actionAmount = 1500; // exceeds balance
        vm.actionReason = 'Возврат по заявке клиента';

        await vm.confirmAction();

        expect(adminApi.refundTenant).not.toHaveBeenCalled();
        expect(vm.actionError).toBe('Сумма возврата превышает баланс тенанта.');
        expect(vm.actionDialog).toBe('refund');
    });

    it('NaN в сумме возврата → actionError, refundTenant не вызывается', async () => {
        vi.mocked(adminApi.listAdminBilling).mockResolvedValueOnce(
            makeBillingResponse([makeApiBillingTenant({ id: 42 })]),
        );

        const wrapper = mountView();
        await flushPromises();

        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const vm = wrapper.vm as any;
        const row = vm.rowsState[0];

        await vm.openAction('refund', row);
        vm.actionAmount = NaN; // non-numeric input from v-model.number
        vm.actionReason = 'Возврат по заявке клиента';

        await vm.confirmAction();

        expect(adminApi.refundTenant).not.toHaveBeenCalled();
        expect(vm.actionError).toBeTruthy();
        expect(vm.actionDialog).toBe('refund');
    });

    it('короткая причина (<10 символов) → confirmAction ставит actionError, не вызывает API', async () => {
        vi.mocked(adminApi.listAdminBilling).mockResolvedValueOnce(
            makeBillingResponse([makeApiBillingTenant({ id: 42 })]),
        );

        const wrapper = mountView();
        await flushPromises();

        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const vm = wrapper.vm as any;
        const row = vm.rowsState[0];

        await vm.openAction('status', row);
        vm.actionReason = 'Коротко'; // < 10 chars

        await vm.confirmAction();

        expect(adminApi.updateTenantStatus).not.toHaveBeenCalled();
        expect(vm.actionError).toBeTruthy();
        expect(vm.actionDialog).toBe('status'); // stays open
    });
});
