/**
 * tenantFeaturesStore.spec.js
 *
 * Cobertura V#34/V#41 — Opção B2 (plano + override com exceção comercial).
 *
 * isEnabled DEVE:
 *  - sem tenantId/key → false (regressão V#34: opt-out por padrão)
 *  - override negativo → false sempre
 *  - override positivo → true sempre (exceção comercial)
 *  - sem override + plano permite → true
 *  - sem override + plano não permite → false
 *  - sem override + user entitlement permite → true (fallback solo therapist)
 *
 * setForTenant DEVE:
 *  - chamar supabase.rpc('set_tenant_feature_exception', ...) com payload correto
 *  - propagar erro
 *  - atualizar cache local em sucesso
 */
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';

const rpcMock = vi.fn();
const fromMock = vi.fn();

vi.mock('@/lib/supabase/client', () => ({
    supabase: {
        from: (...args) => fromMock(...args),
        rpc: (...args) => rpcMock(...args)
    }
}));

// estado controlável do entitlementsStore
const entState = {
    loadedForTenant: null,
    tenantFeatures: [],
    userFeatures: []
};

vi.mock('@/stores/entitlementsStore', () => ({
    useEntitlementsStore: () => entState
}));

const { useTenantFeaturesStore } = await import('../tenantFeaturesStore.js');

beforeEach(() => {
    setActivePinia(createPinia());
    rpcMock.mockReset();
    fromMock.mockReset();
    entState.loadedForTenant = null;
    entState.tenantFeatures = [];
    entState.userFeatures = [];
});

describe('isEnabled — guard rails', () => {
    it('retorna false sem tenantId nem loadedForTenantId', () => {
        const s = useTenantFeaturesStore();
        expect(s.isEnabled('patients')).toBe(false);
    });

    it('retorna false sem key', () => {
        const s = useTenantFeaturesStore();
        s.featuresByTenant['t1'] = { patients: true };
        s.loadedForTenantId = 't1';
        expect(s.isEnabled('')).toBe(false);
        expect(s.isEnabled(null, 't1')).toBe(false);
    });

    it('regressão V#34: NÃO retorna true por padrão quando feature ausente', () => {
        // Antes (bug): sem entry em tenant_features → isEnabled = true
        // Agora (B2): sem entry + plano não permite → false
        const s = useTenantFeaturesStore();
        s.featuresByTenant['t1'] = {}; // sem entries
        s.loadedForTenantId = 't1';
        // entitlements vazio → não permite
        expect(s.isEnabled('patients', 't1')).toBe(false);
    });
});

describe('isEnabled — override explícito tem precedência', () => {
    it('override positivo → true mesmo se plano não permite (exceção comercial)', () => {
        const s = useTenantFeaturesStore();
        s.featuresByTenant['t1'] = { 'documents.signatures': true };
        s.loadedForTenantId = 't1';
        // entitlements vazio para esse tenant
        entState.loadedForTenant = 't1';
        entState.tenantFeatures = [];
        expect(s.isEnabled('documents.signatures', 't1')).toBe(true);
    });

    it('override negativo → false mesmo se plano permite (preferência cliente)', () => {
        const s = useTenantFeaturesStore();
        s.featuresByTenant['t1'] = { patients: false };
        s.loadedForTenantId = 't1';
        entState.loadedForTenant = 't1';
        entState.tenantFeatures = ['patients'];
        expect(s.isEnabled('patients', 't1')).toBe(false);
    });
});

describe('isEnabled — sem override → segue plano', () => {
    it('plano (tenant entitlement) permite → true', () => {
        const s = useTenantFeaturesStore();
        s.featuresByTenant['t1'] = {};
        s.loadedForTenantId = 't1';
        entState.loadedForTenant = 't1';
        entState.tenantFeatures = ['patients', 'agenda.view'];
        expect(s.isEnabled('patients', 't1')).toBe(true);
        expect(s.isEnabled('agenda.view', 't1')).toBe(true);
    });

    it('plano não permite → false', () => {
        const s = useTenantFeaturesStore();
        s.featuresByTenant['t1'] = {};
        s.loadedForTenantId = 't1';
        entState.loadedForTenant = 't1';
        entState.tenantFeatures = ['patients'];
        expect(s.isEnabled('documents.signatures', 't1')).toBe(false);
    });

    it('fallback user entitlement (solo therapist) → true', () => {
        const s = useTenantFeaturesStore();
        s.featuresByTenant['t1'] = {};
        s.loadedForTenantId = 't1';
        // tenant não tem subscription, mas user tem (therapist_pro)
        entState.loadedForTenant = null;
        entState.tenantFeatures = [];
        entState.userFeatures = ['patients.manage', 'documents.signatures'];
        expect(s.isEnabled('patients.manage', 't1')).toBe(true);
        expect(s.isEnabled('documents.signatures', 't1')).toBe(true);
    });

    it('entitlements carregado para OUTRO tenant → ignora tenantFeatures (fail-safe)', () => {
        const s = useTenantFeaturesStore();
        s.featuresByTenant['t1'] = {};
        s.loadedForTenantId = 't1';
        // ent carregado para t2, não t1 → não confiar em tenantFeatures
        entState.loadedForTenant = 't2';
        entState.tenantFeatures = ['patients'];
        // userFeatures vazio também
        expect(s.isEnabled('patients', 't1')).toBe(false);
    });
});

describe('setForTenant — RPC obrigatória', () => {
    it('rejeita sem tenantId', async () => {
        const s = useTenantFeaturesStore();
        await expect(s.setForTenant(null, 'patients', true)).rejects.toThrow(/tenantId/);
    });

    it('rejeita sem key', async () => {
        const s = useTenantFeaturesStore();
        await expect(s.setForTenant('t1', '', true)).rejects.toThrow(/feature_key/);
    });

    it('chama RPC set_tenant_feature_exception com payload correto', async () => {
        rpcMock.mockResolvedValue({ data: { ok: true }, error: null });

        const s = useTenantFeaturesStore();
        await s.setForTenant('t1', 'patients', false, { reason: 'pref do cliente' });

        expect(rpcMock).toHaveBeenCalledWith('set_tenant_feature_exception', {
            p_tenant_id: 't1',
            p_feature_key: 'patients',
            p_enabled: false,
            p_reason: 'pref do cliente'
        });
    });

    it('aceita reason omitido (vira null)', async () => {
        rpcMock.mockResolvedValue({ data: null, error: null });
        const s = useTenantFeaturesStore();
        await s.setForTenant('t1', 'patients', true);
        expect(rpcMock).toHaveBeenCalledWith('set_tenant_feature_exception', {
            p_tenant_id: 't1',
            p_feature_key: 'patients',
            p_enabled: true,
            p_reason: null
        });
    });

    it('propaga erro do RPC', async () => {
        rpcMock.mockResolvedValue({ data: null, error: new Error('Apenas saas_admin pode liberar feature fora do plano') });
        const s = useTenantFeaturesStore();
        await expect(s.setForTenant('t1', 'documents.signatures', true)).rejects.toThrow(/saas_admin/);
        expect(s.lastError).toBeTruthy();
    });

    it('atualiza cache local em sucesso', async () => {
        rpcMock.mockResolvedValue({ data: { ok: true }, error: null });
        const s = useTenantFeaturesStore();
        await s.setForTenant('t1', 'patients', false, { reason: 'test' });
        // cache local refletiu
        expect(s.featuresByTenant['t1']?.patients).toBe(false);
        expect(s.loadedForTenantId).toBe('t1');
    });
});

describe('invalidate — limpeza por tenant', () => {
    it('sem tenantId limpa tudo', () => {
        const s = useTenantFeaturesStore();
        s.featuresByTenant['t1'] = { patients: true };
        s.featuresByTenant['t2'] = { rooms: false };
        s.loadedForTenantId = 't1';
        s.invalidate();
        expect(Object.keys(s.featuresByTenant).length).toBe(0);
        expect(s.loadedForTenantId).toBe(null);
    });

    it('com tenantId limpa só esse', () => {
        const s = useTenantFeaturesStore();
        s.featuresByTenant['t1'] = { patients: true };
        s.featuresByTenant['t2'] = { rooms: false };
        s.loadedForTenantId = 't1';
        s.invalidate('t1');
        expect(s.featuresByTenant['t1']).toBeUndefined();
        expect(s.featuresByTenant['t2']).toEqual({ rooms: false });
        expect(s.loadedForTenantId).toBe(null);
    });
});
