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

vi.mock('../../resources/js/api/client', () => ({
    apiClient: {
        get: vi.fn(),
        post: vi.fn(),
        put: vi.fn(),
        patch: vi.fn(),
        delete: vi.fn(),
    },
    ensureCsrfCookie: vi.fn(),
}));

import {
    impersonationInit,
    impersonationVerify,
    impersonationEnd,
    impersonationActive,
    impersonationRecent,
    listAdminTenants,
    getAdminTenantDetail,
    listAdminBilling,
    listAdminIncidents,
    listSystemSettings,
    updateSystemSetting,
    listAdminTariffPlans,
    updateTenantStatus,
    refundTenant,
    changeTenantTariff,
    getAdminIncidentDetail,
    notifyIncidentRkn,
} from '../../resources/js/api/admin';
import { apiClient, ensureCsrfCookie } from '../../resources/js/api/client';

const FAKE_TENANT = {
    id: 1,
    subdomain: 'demo',
    organization_name: 'Demo Org',
    contact_email: 'demo@x.ru',
    status: 'active',
    balance_rub: '1000.00',
    balance_leads: 0,
    is_trial: false,
    last_activity_at: null,
    tariff_id: 1,
    tariff_name: 'basic',
    mrr_rub: '5000.00',
    desired_daily_numbers: 10,
    chargeback_unrecovered_rub: '0.00',
    created_at: '2026-05-01T00:00:00Z',
};

describe('api/admin', () => {
    beforeEach(() => vi.clearAllMocks());

    it('impersonationInit() POSTs /api/admin/impersonation/init + ensureCsrfCookie ПЕРЕД post', async () => {
        vi.mocked(apiClient.post).mockResolvedValue({
            data: {
                token_id: 42,
                expires_at: '2026-05-14T13:00:00Z',
                sent_to_email: 'admin@x.ru',
                _dev_plain_code: '123456',
            },
        });
        const payload = {
            tenant_id: 1,
            requested_by: 9,
            reason: 'Investigating webhook delivery failure for tenant',
        };
        const r = await impersonationInit(payload);
        expect(ensureCsrfCookie).toHaveBeenCalledOnce();
        expect(apiClient.post).toHaveBeenCalledWith('/api/admin/impersonation/init', payload);
        // verify ensureCsrfCookie вызван ДО apiClient.post
        const csrfOrder = vi.mocked(ensureCsrfCookie).mock.invocationCallOrder[0];
        const postOrder = vi.mocked(apiClient.post).mock.invocationCallOrder[0];
        expect(csrfOrder).toBeLessThan(postOrder);
        expect(r.token_id).toBe(42);
        expect(r._dev_plain_code).toBe('123456');
    });

    it('impersonationVerify() POSTs /api/admin/impersonation/verify + ensureCsrfCookie ПЕРЕД post', async () => {
        vi.mocked(apiClient.post).mockResolvedValue({
            data: {
                token_id: 42,
                tenant_id: 1,
                used_at: '2026-05-14T12:30:00Z',
                message: 'verified',
            },
        });
        const payload = { token_id: 42, code: '123456' };
        const r = await impersonationVerify(payload);
        expect(ensureCsrfCookie).toHaveBeenCalledOnce();
        expect(apiClient.post).toHaveBeenCalledWith('/api/admin/impersonation/verify', payload);
        const csrfOrder = vi.mocked(ensureCsrfCookie).mock.invocationCallOrder[0];
        const postOrder = vi.mocked(apiClient.post).mock.invocationCallOrder[0];
        expect(csrfOrder).toBeLessThan(postOrder);
        expect(r.message).toBe('verified');
    });

    it('impersonationEnd() POSTs /api/admin/impersonation/end с {token_id} + ensureCsrfCookie ПЕРЕД post', async () => {
        vi.mocked(apiClient.post).mockResolvedValue({
            data: {
                token_id: 42,
                session_ended_at: '2026-05-14T13:00:00Z',
                message: 'ended',
            },
        });
        const r = await impersonationEnd(42);
        expect(ensureCsrfCookie).toHaveBeenCalledOnce();
        expect(apiClient.post).toHaveBeenCalledWith('/api/admin/impersonation/end', { token_id: 42 });
        const csrfOrder = vi.mocked(ensureCsrfCookie).mock.invocationCallOrder[0];
        const postOrder = vi.mocked(apiClient.post).mock.invocationCallOrder[0];
        expect(csrfOrder).toBeLessThan(postOrder);
        expect(r.token_id).toBe(42);
    });

    it('impersonationActive() GET /api/admin/impersonation/active + unwraps data.sessions', async () => {
        const session = {
            token_id: 1,
            tenant_id: 1,
            tenant_name: 'Demo',
            requested_by: 9,
            reason: 'r',
            sent_to_email: 'a@x.ru',
            used_at: '2026-05-14T12:00:00Z',
            expires_at: '2026-05-14T13:00:00Z',
        };
        vi.mocked(apiClient.get).mockResolvedValue({ data: { sessions: [session] } });
        const r = await impersonationActive();
        expect(apiClient.get).toHaveBeenCalledWith('/api/admin/impersonation/active');
        expect(r).toHaveLength(1);
        expect(r[0].token_id).toBe(1);
    });

    it('impersonationRecent() GET /api/admin/impersonation/recent + unwraps data.sessions', async () => {
        const session = {
            token_id: 2,
            tenant_id: 1,
            tenant_name: 'Demo',
            requested_by: 9,
            reason: 'r',
            used_at: '2026-05-14T10:00:00Z',
            session_ended_at: '2026-05-14T11:00:00Z',
            duration_seconds: 3600,
        };
        vi.mocked(apiClient.get).mockResolvedValue({ data: { sessions: [session] } });
        const r = await impersonationRecent();
        expect(apiClient.get).toHaveBeenCalledWith('/api/admin/impersonation/recent');
        expect(r[0].duration_seconds).toBe(3600);
    });

    it('listAdminTenants() без params → default {}', async () => {
        vi.mocked(apiClient.get).mockResolvedValue({
            data: {
                tenants: [],
                total: 0,
                limit: 50,
                offset: 0,
                stats: { total: 0, active: 0, trial: 0, overdue: 0 },
            },
        });
        await listAdminTenants();
        expect(apiClient.get).toHaveBeenCalledWith('/api/admin/tenants', { params: {} });
    });

    it('listAdminTenants({status,search,limit,offset}) → передаёт params', async () => {
        vi.mocked(apiClient.get).mockResolvedValue({
            data: {
                tenants: [FAKE_TENANT],
                total: 1,
                limit: 25,
                offset: 0,
                stats: { total: 1, active: 1, trial: 0, overdue: 0 },
            },
        });
        const params = { status: 'active', search: 'demo', limit: 25, offset: 0 };
        const r = await listAdminTenants(params);
        expect(apiClient.get).toHaveBeenCalledWith('/api/admin/tenants', { params });
        expect(r.tenants).toHaveLength(1);
        expect(r.tenants[0].subdomain).toBe('demo');
    });

    it('getAdminTenantDetail() GET /api/admin/tenants/{subdomain} с обычным slug', async () => {
        vi.mocked(apiClient.get).mockResolvedValue({
            data: {
                tenant: FAKE_TENANT,
                users: [],
                projects: [],
                balance_history: [],
                activity: [],
                metrics: {
                    leads_today: 0,
                    leads_this_week: 0,
                    leads_this_month: 0,
                    avg_lead_cost_rub: null,
                    runway_days: null,
                },
            },
        });
        const r = await getAdminTenantDetail('demo');
        expect(apiClient.get).toHaveBeenCalledWith('/api/admin/tenants/demo');
        expect(r.tenant.id).toBe(1);
    });

    it('getAdminTenantDetail() применяет encodeURIComponent к slug со спецсимволами', async () => {
        vi.mocked(apiClient.get).mockResolvedValue({
            data: {
                tenant: FAKE_TENANT,
                users: [],
                projects: [],
                balance_history: [],
                activity: [],
                metrics: {
                    leads_today: 0,
                    leads_this_week: 0,
                    leads_this_month: 0,
                    avg_lead_cost_rub: null,
                    runway_days: null,
                },
            },
        });
        await getAdminTenantDetail('tenant a&b');
        expect(apiClient.get).toHaveBeenCalledWith('/api/admin/tenants/tenant%20a%26b');
    });

    it('listAdminBilling() без аргумента → search=""', async () => {
        vi.mocked(apiClient.get).mockResolvedValue({
            data: {
                tenants: [],
                summary: {
                    total_mrr_rub: '0.00',
                    monthly_revenue_rub: '0.00',
                    overdue_count: 0,
                    refunds_count_30d: 0,
                },
            },
        });
        await listAdminBilling();
        expect(apiClient.get).toHaveBeenCalledWith('/api/admin/billing', { params: { search: '' } });
    });

    it('listAdminBilling("query") → передаёт search в params', async () => {
        vi.mocked(apiClient.get).mockResolvedValue({
            data: {
                tenants: [],
                summary: {
                    total_mrr_rub: '100.00',
                    monthly_revenue_rub: '500.00',
                    overdue_count: 1,
                    refunds_count_30d: 0,
                },
            },
        });
        const r = await listAdminBilling('demo');
        expect(apiClient.get).toHaveBeenCalledWith('/api/admin/billing', { params: { search: 'demo' } });
        expect(r.summary.overdue_count).toBe(1);
    });

    it('listAdminIncidents() без params → default {}', async () => {
        vi.mocked(apiClient.get).mockResolvedValue({
            data: {
                incidents: [],
                total: 0,
                limit: 50,
                offset: 0,
                summary: { open: 0, investigating: 0, rkn_pending: 0, total_unresolved: 0 },
            },
        });
        await listAdminIncidents();
        expect(apiClient.get).toHaveBeenCalledWith('/api/admin/incidents', { params: {} });
    });

    it('listAdminIncidents({type,severity,unresolved_only,limit,offset}) → передаёт params', async () => {
        vi.mocked(apiClient.get).mockResolvedValue({
            data: {
                incidents: [],
                total: 0,
                limit: 10,
                offset: 0,
                summary: { open: 0, investigating: 0, rkn_pending: 0, total_unresolved: 0 },
            },
        });
        const params = { type: 'pdn_leak', severity: 'high', unresolved_only: true, limit: 10, offset: 0 };
        await listAdminIncidents(params);
        expect(apiClient.get).toHaveBeenCalledWith('/api/admin/incidents', { params });
    });

    it('listSystemSettings() GET /api/admin/system-settings + unwraps data.settings', async () => {
        const settings = [
            {
                key: 'lead.price_default',
                value: '500',
                type: 'decimal' as const,
                description: 'default',
                updated_at: '2026-05-14T00:00:00Z',
                updated_by: 1,
            },
        ];
        vi.mocked(apiClient.get).mockResolvedValue({ data: { settings } });
        const r = await listSystemSettings();
        expect(apiClient.get).toHaveBeenCalledWith('/api/admin/system-settings');
        expect(r).toHaveLength(1);
        expect(r[0].key).toBe('lead.price_default');
    });

    it('updateSystemSetting() PUTs /api/admin/system-settings/{key} + encodeURIComponent + ensureCsrfCookie ПЕРЕД put', async () => {
        vi.mocked(apiClient.put).mockResolvedValue({
            data: {
                key: 'lead.price_default',
                value: '600',
                previous_value: '500',
                updated_at: '2026-05-14T12:00:00Z',
                message: 'updated',
            },
        });
        const payload = {
            value: '600',
            reason: 'Quarterly pricing adjustment for Q2 according to bizdev',
            admin_user_id: 1,
        };
        const r = await updateSystemSetting('lead.price_default', payload);
        expect(ensureCsrfCookie).toHaveBeenCalledOnce();
        expect(apiClient.put).toHaveBeenCalledWith('/api/admin/system-settings/lead.price_default', payload);
        const csrfOrder = vi.mocked(ensureCsrfCookie).mock.invocationCallOrder[0];
        const putOrder = vi.mocked(apiClient.put).mock.invocationCallOrder[0];
        expect(csrfOrder).toBeLessThan(putOrder);
        expect(r.previous_value).toBe('500');
    });

    it('updateSystemSetting() encodeURIComponent применяется к ключу со спецсимволами', async () => {
        vi.mocked(apiClient.put).mockResolvedValue({
            data: {
                key: 'a/b',
                value: 'v',
                previous_value: 'p',
                updated_at: '2026-05-14T12:00:00Z',
                message: 'ok',
            },
        });
        const payload = { value: 'v', reason: 'r'.repeat(40), admin_user_id: 1 };
        await updateSystemSetting('a/b', payload);
        expect(apiClient.put).toHaveBeenCalledWith('/api/admin/system-settings/a%2Fb', payload);
    });

    // === Error propagation ===

    it('impersonationInit() пробрасывает ошибку из apiClient.post (не глотает)', async () => {
        vi.mocked(apiClient.post).mockRejectedValueOnce(new Error('Network'));
        await expect(impersonationInit({ tenant_id: 1, requested_by: 9, reason: 'r'.repeat(30) })).rejects.toThrow(
            'Network',
        );
        expect(ensureCsrfCookie).toHaveBeenCalledOnce();
    });

    it('listAdminTenants() пробрасывает ошибку из apiClient.get (не глотает)', async () => {
        vi.mocked(apiClient.get).mockRejectedValueOnce(new Error('500 Server Error'));
        await expect(listAdminTenants({ status: 'active' })).rejects.toThrow('500 Server Error');
    });

    // === Sprint 3D G4: billing row-actions ===

    it('listAdminTariffPlans() GET /api/admin/billing/tariff-plans + unwraps data.plans', async () => {
        const plans = [{ id: 1, name: 'Базовый', price_monthly: '990.00' }];
        vi.mocked(apiClient.get).mockResolvedValue({ data: { plans } });
        const r = await listAdminTariffPlans();
        expect(apiClient.get).toHaveBeenCalledWith('/api/admin/billing/tariff-plans');
        expect(r).toHaveLength(1);
        expect(r[0].name).toBe('Базовый');
    });

    it('updateTenantStatus() PATCH /api/admin/billing/tenants/{id}/status + ensureCsrfCookie', async () => {
        vi.mocked(apiClient.patch).mockResolvedValue({ data: { id: 5, status: 'suspended' } });
        const r = await updateTenantStatus(5, 'suspended', 'Нарушение условий');
        expect(ensureCsrfCookie).toHaveBeenCalledOnce();
        expect(apiClient.patch).toHaveBeenCalledWith('/api/admin/billing/tenants/5/status', {
            status: 'suspended',
            reason: 'Нарушение условий',
        });
        expect(r.status).toBe('suspended');
    });

    it('refundTenant() POST /api/admin/billing/tenants/{id}/refund + ensureCsrfCookie', async () => {
        vi.mocked(apiClient.post).mockResolvedValue({
            data: { id: 3, balance_rub: '5000.00', transaction_id: 101 },
        });
        const r = await refundTenant(3, 1000, 'Возврат по заявке');
        expect(ensureCsrfCookie).toHaveBeenCalledOnce();
        expect(apiClient.post).toHaveBeenCalledWith('/api/admin/billing/tenants/3/refund', {
            amount_rub: 1000,
            reason: 'Возврат по заявке',
        });
        expect(r.transaction_id).toBe(101);
    });

    it('changeTenantTariff() PATCH /api/admin/billing/tenants/{id}/tariff + ensureCsrfCookie', async () => {
        vi.mocked(apiClient.patch).mockResolvedValue({
            data: { id: 2, tariff_id: 3, tariff_name: 'Команда' },
        });
        const r = await changeTenantTariff(2, 3, 'Апгрейд по договорённости');
        expect(ensureCsrfCookie).toHaveBeenCalledOnce();
        expect(apiClient.patch).toHaveBeenCalledWith('/api/admin/billing/tenants/2/tariff', {
            tariff_id: 3,
            reason: 'Апгрейд по договорённости',
        });
        expect(r.tariff_name).toBe('Команда');
    });

    // === Sprint 3D G5/G6: incident detail + РКН-notify ===

    it('getAdminIncidentDetail() GET /api/admin/incidents/{id} + unwraps data.incident', async () => {
        const incident = {
            id: 7,
            incident_id: 'INC-2026-0516-0007',
            type: 'data_breach',
            severity: 'high',
            summary: 'Тест',
            root_cause: null,
            postmortem_url: null,
            started_at: '2026-05-16T10:00:00Z',
            detected_at: '2026-05-16T10:30:00Z',
            resolved_at: null,
            status: 'investigating',
            affected_tenants: [],
            affected_users_count: null,
            notification_sent_at: null,
            rkn_notified: false,
            rkn_notified_at: null,
            rkn_deadline_at: null,
            created_by_admin: null,
            closed_by_admin: null,
            created_at: null,
            updated_at: null,
        };
        vi.mocked(apiClient.get).mockResolvedValue({ data: { incident } });
        const r = await getAdminIncidentDetail(7);
        expect(apiClient.get).toHaveBeenCalledWith('/api/admin/incidents/7');
        expect(r.incident_id).toBe('INC-2026-0516-0007');
        expect(r.id).toBe(7);
    });

    it('notifyIncidentRkn() POST /api/admin/incidents/{id}/rkn-notify + ensureCsrfCookie + unwraps data.incident', async () => {
        const incident = {
            id: 7,
            incident_id: 'INC-2026-0516-0007',
            type: 'data_breach',
            severity: 'high',
            summary: 'Тест',
            root_cause: null,
            postmortem_url: null,
            started_at: '2026-05-16T10:00:00Z',
            detected_at: '2026-05-16T10:30:00Z',
            resolved_at: null,
            status: 'investigating',
            affected_tenants: [],
            affected_users_count: null,
            notification_sent_at: '2026-05-16T11:00:00Z',
            rkn_notified: true,
            rkn_notified_at: '2026-05-16T11:00:00Z',
            rkn_deadline_at: '2026-05-17T10:30:00Z',
            created_by_admin: null,
            closed_by_admin: null,
            created_at: null,
            updated_at: null,
        };
        vi.mocked(apiClient.post).mockResolvedValue({ data: { incident } });
        const r = await notifyIncidentRkn(7);
        expect(ensureCsrfCookie).toHaveBeenCalledOnce();
        expect(apiClient.post).toHaveBeenCalledWith('/api/admin/incidents/7/rkn-notify', {});
        expect(r.rkn_notified).toBe(true);
        expect(r.rkn_notified_at).toBe('2026-05-16T11:00:00Z');
    });
});
