/**
 * P1-2 — rule conditions (consecutiveFailures, latencyThreshold) are
 * actually consulted now that monitor-engine populates the relevant
 * fields. Pin the contract in case future refactors drop them again.
 */

jest.mock('@/lib/prisma', () => ({
    prisma: {
        notificationRule: { findMany: jest.fn() },
        notificationChannel: { findMany: jest.fn().mockResolvedValue([]) },
    },
}));

import { RulesEngineService, invalidateRulesCache, type RuleContext } from '../rules-engine.service';
import { prisma } from '@/lib/prisma';

const findMany = prisma.notificationRule.findMany as jest.Mock;

function rule(overrides: Partial<{ conditions: string }> = {}) {
    return {
        id: 1,
        name: 'test',
        enabled: true,
        scope: 'global',
        scopeId: null,
        monitorTypes: null,
        triggers: [{ event: 'monitor_down' }],
        monitors: [],
        country: null,
        user: null,
        channelIds: null,
        conditions: overrides.conditions ?? null,
        schedule: null,
    };
}

function ctx(overrides: Partial<RuleContext> = {}): RuleContext {
    return {
        trigger: 'monitor_down',
        monitorId: 1,
        monitorType: 'http',
        monitorRegion: 'KE',
        ...overrides,
    };
}

// audit3-2 (2026-05-28): RulesEngineService caches findMany on a 30 s
// TTL. Each test below mocks a DIFFERENT findMany return value via
// mockResolvedValueOnce; without invalidating the cache between tests,
// only the first test's mock is consumed and the rest hit stale data.
beforeEach(() => {
    jest.clearAllMocks();
    invalidateRulesCache();
});

describe('RulesEngineService conditions (P1-2)', () => {
    it('latencyThreshold matches when latency >= threshold', async () => {
        findMany.mockResolvedValueOnce([rule({ conditions: JSON.stringify({ latencyThreshold: 500 }) })]);
        const matched = await RulesEngineService.evaluate(ctx({ latency: 800 }));
        expect(matched.length).toBe(1);
    });

    it('latencyThreshold rejects when latency < threshold', async () => {
        findMany.mockResolvedValueOnce([rule({ conditions: JSON.stringify({ latencyThreshold: 500 }) })]);
        const matched = await RulesEngineService.evaluate(ctx({ latency: 100 }));
        expect(matched.length).toBe(0);
    });

    it('consecutiveFailures matches when consecutiveStatus >= threshold', async () => {
        findMany.mockResolvedValueOnce([rule({ conditions: JSON.stringify({ consecutiveFailures: 3 }) })]);
        const matched = await RulesEngineService.evaluate(ctx({ consecutiveStatus: 5 }));
        expect(matched.length).toBe(1);
    });

    it('consecutiveFailures rejects when consecutiveStatus < threshold', async () => {
        findMany.mockResolvedValueOnce([rule({ conditions: JSON.stringify({ consecutiveFailures: 5 }) })]);
        const matched = await RulesEngineService.evaluate(ctx({ consecutiveStatus: 3 }));
        expect(matched.length).toBe(0);
    });

    it('rule with no conditions always matches', async () => {
        findMany.mockResolvedValueOnce([rule()]);
        const matched = await RulesEngineService.evaluate(ctx());
        expect(matched.length).toBe(1);
    });

    // Theme B (P1-14): errorClass condition matching
    it('errorClass matches when context.errorClass === conditions.errorClass', async () => {
        findMany.mockResolvedValueOnce([rule({ conditions: JSON.stringify({ errorClass: 'TLS_INVALID' }) })]);
        const matched = await RulesEngineService.evaluate(ctx({ errorClass: 'TLS_INVALID' }));
        expect(matched.length).toBe(1);
    });

    it('errorClass rejects when context.errorClass is different', async () => {
        findMany.mockResolvedValueOnce([rule({ conditions: JSON.stringify({ errorClass: 'TLS_INVALID' }) })]);
        const matched = await RulesEngineService.evaluate(ctx({ errorClass: 'HTTP_5XX' }));
        expect(matched.length).toBe(0);
    });

    it('errorClass rejects when context.errorClass is missing', async () => {
        findMany.mockResolvedValueOnce([rule({ conditions: JSON.stringify({ errorClass: 'TLS_INVALID' }) })]);
        const matched = await RulesEngineService.evaluate(ctx({}));
        expect(matched.length).toBe(0);
    });

    it('errorClassIn allow-list matches when context.errorClass is in the list', async () => {
        findMany.mockResolvedValueOnce([
            rule({ conditions: JSON.stringify({ errorClassIn: ['DNS_FAIL', 'TLS_INVALID'] }) }),
        ]);
        const matched = await RulesEngineService.evaluate(ctx({ errorClass: 'DNS_FAIL' }));
        expect(matched.length).toBe(1);
    });

    it('errorClassIn rejects when context.errorClass not in the list', async () => {
        findMany.mockResolvedValueOnce([
            rule({ conditions: JSON.stringify({ errorClassIn: ['DNS_FAIL', 'TLS_INVALID'] }) }),
        ]);
        const matched = await RulesEngineService.evaluate(ctx({ errorClass: 'HTTP_5XX' }));
        expect(matched.length).toBe(0);
    });
});
