/**
 * alerting.ts 단위 테스트
 *
 * 테스트 대상:
 *  - checkDailyCostAlert(): 비용 미초과/초과/쿼리 한도 초과
 *  - detectAnomalousTraffic(): 정상/스파이크/평균 0 케이스
 */

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

// ─────────────────────────────────────────────
// vi.hoisted()로 mock 함수 선언 (hoisting 안전)
// ─────────────────────────────────────────────

const { mockGetDailyUsage } = vi.hoisted(() => {
    return { mockGetDailyUsage: vi.fn() };
});

vi.mock('@/lib/monitoring/costMonitor', () => ({
    getDailyUsage: mockGetDailyUsage,
}));

vi.mock('server-only', () => ({}));

import {
    checkDailyCostAlert,
    detectAnomalousTraffic,
    ALERT_THRESHOLDS,
} from '../alerting';

// ─────────────────────────────────────────────
// beforeEach: mock 초기화
// ─────────────────────────────────────────────

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

// ─────────────────────────────────────────────
// checkDailyCostAlert 테스트
// ─────────────────────────────────────────────

describe('checkDailyCostAlert', () => {
    it('비용 미초과 시 isAlert=false를 반환해야 한다', async () => {
        mockGetDailyUsage.mockResolvedValue({
            estimatedCostUsd: 1.0,    // 임계값($5) 미만
            queryCalls: 100,           // 한도(5000) 미만
            hourlyQueryCounts: {},
        });

        const result = await checkDailyCostAlert('2026-03-01');

        expect(result.isAlert).toBe(false);
        expect(result.currentCost).toBe(1.0);
        expect(result.threshold).toBe(ALERT_THRESHOLDS.dailyCostUsd);
        expect(result.message).toBeUndefined();
    });

    it('비용 초과 시 isAlert=true + 올바른 message를 반환해야 한다', async () => {
        const overCost = 6.0;
        mockGetDailyUsage.mockResolvedValue({
            estimatedCostUsd: overCost,  // $5 초과
            queryCalls: 100,
            hourlyQueryCounts: {},
        });

        const result = await checkDailyCostAlert('2026-03-01');

        expect(result.isAlert).toBe(true);
        expect(result.currentCost).toBe(overCost);
        expect(result.message).toBeDefined();
        expect(result.message).toContain('[COST ALERT]');
        expect(result.message).toContain('2026-03-01');
        expect(result.message).toContain('일일 비용 임계값 초과');
    });

    it('쿼리 한도 초과 시 isAlert=true를 반환해야 한다', async () => {
        mockGetDailyUsage.mockResolvedValue({
            estimatedCostUsd: 0.5,     // 비용은 정상
            queryCalls: 6000,           // 5000 초과
            hourlyQueryCounts: {},
        });

        const result = await checkDailyCostAlert('2026-03-01');

        expect(result.isAlert).toBe(true);
        expect(result.message).toBeDefined();
        expect(result.message).toContain('일일 쿼리 한도 초과');
        expect(result.message).toContain('6000');
    });

    it('비용 초과 + 쿼리 한도 초과 시 두 이유 모두 message에 포함되어야 한다', async () => {
        mockGetDailyUsage.mockResolvedValue({
            estimatedCostUsd: 10.0,
            queryCalls: 6000,
            hourlyQueryCounts: {},
        });

        const result = await checkDailyCostAlert('2026-03-01');

        expect(result.isAlert).toBe(true);
        expect(result.message).toContain('일일 비용 임계값 초과');
        expect(result.message).toContain('일일 쿼리 한도 초과');
    });

    it('usage 데이터가 없을 때(null) isAlert=false를 반환해야 한다', async () => {
        mockGetDailyUsage.mockResolvedValue(null);

        const result = await checkDailyCostAlert('2026-03-01');

        expect(result.isAlert).toBe(false);
        expect(result.currentCost).toBe(0);
    });

    it('정확히 임계값과 동일한 비용($5.0)은 초과가 아니어야 한다', async () => {
        mockGetDailyUsage.mockResolvedValue({
            estimatedCostUsd: 5.0,     // 정확히 임계값 (초과가 아님)
            queryCalls: 100,
            hourlyQueryCounts: {},
        });

        const result = await checkDailyCostAlert('2026-03-01');

        expect(result.isAlert).toBe(false);
    });
});

// ─────────────────────────────────────────────
// detectAnomalousTraffic 테스트
// ─────────────────────────────────────────────

describe('detectAnomalousTraffic', () => {
    it('정상 트래픽 시 isAnomaly=false를 반환해야 한다', async () => {
        // 모든 시간대 동일 → 평균과 현재가 동일 → spikeFactor=1.0 < 3.0
        const currentHour = new Date().getUTCHours().toString().padStart(2, '0');
        const counts: Record<string, number> = {};
        for (let i = 0; i < 6; i++) {
            const h = ((new Date().getUTCHours() + i) % 24).toString().padStart(2, '0');
            counts[h] = 10;
        }

        mockGetDailyUsage.mockResolvedValue({
            estimatedCostUsd: 0.1,
            queryCalls: 60,
            hourlyQueryCounts: counts,
        });

        const result = await detectAnomalousTraffic('2026-03-01');

        expect(result.isAnomaly).toBe(false);
        // spikeFactor = 현재/평균 = 10/10 = 1.0
        expect(result.spikeFactor).toBeLessThan(ALERT_THRESHOLDS.hourlyQuerySpike);
        // currentHour 값 확인
        expect(result.currentHourlyRate).toBe(counts[currentHour]);
    });

    it('스파이크 시 isAnomaly=true + 올바른 spikeFactor를 반환해야 한다', async () => {
        // 현재 시간대=30, 나머지5개=1 → avg=(30+5)/6=35/6≈5.83, spikeFactor=30/5.83≈5.14
        const currentHour = new Date().getUTCHours().toString().padStart(2, '0');
        const counts: Record<string, number> = { [currentHour]: 30 };
        for (let i = 1; i <= 5; i++) {
            const h = ((new Date().getUTCHours() + i) % 24).toString().padStart(2, '0');
            counts[h] = 1;
        }

        mockGetDailyUsage.mockResolvedValue({
            estimatedCostUsd: 0.5,
            queryCalls: 35,
            hourlyQueryCounts: counts,
        });

        const result = await detectAnomalousTraffic('2026-03-01');

        expect(result.isAnomaly).toBe(true);
        expect(result.spikeFactor).toBeGreaterThanOrEqual(ALERT_THRESHOLDS.hourlyQuerySpike);
        expect(result.message).toBeDefined();
        expect(result.message).toContain('[TRAFFIC ANOMALY]');
        expect(result.currentHourlyRate).toBe(30);
    });

    it('평균이 0일 때 spikeFactor=0이고 이상 없음이어야 한다 (division by zero 방지)', async () => {
        // hourlyQueryCounts가 비어 있으면 평균=0
        mockGetDailyUsage.mockResolvedValue({
            estimatedCostUsd: 0,
            queryCalls: 0,
            hourlyQueryCounts: {},
        });

        const result = await detectAnomalousTraffic('2026-03-01');

        expect(result.spikeFactor).toBe(0);
        expect(result.isAnomaly).toBe(false);
        expect(result.averageHourlyRate).toBe(0);
    });

    it('usage가 null일 때 spikeFactor=0이고 이상 없음이어야 한다', async () => {
        mockGetDailyUsage.mockResolvedValue(null);

        const result = await detectAnomalousTraffic('2026-03-01');

        expect(result.spikeFactor).toBe(0);
        expect(result.isAnomaly).toBe(false);
    });

    it('currentHourlyRate, averageHourlyRate, spikeFactor, isAnomaly가 응답에 포함되어야 한다', async () => {
        const currentHour = new Date().getUTCHours().toString().padStart(2, '0');
        mockGetDailyUsage.mockResolvedValue({
            estimatedCostUsd: 0,
            queryCalls: 5,
            hourlyQueryCounts: { [currentHour]: 5 },
        });

        const result = await detectAnomalousTraffic('2026-03-01');

        expect(result).toHaveProperty('currentHourlyRate');
        expect(result).toHaveProperty('averageHourlyRate');
        expect(result).toHaveProperty('spikeFactor');
        expect(result).toHaveProperty('isAnomaly');
    });
});

// ─────────────────────────────────────────────
// ALERT_THRESHOLDS 상수 테스트
// ─────────────────────────────────────────────

describe('ALERT_THRESHOLDS 상수', () => {
    it('dailyCostUsd 임계값이 양수이어야 한다', () => {
        expect(ALERT_THRESHOLDS.dailyCostUsd).toBeGreaterThan(0);
    });

    it('hourlyQuerySpike 임계값이 1보다 커야 한다', () => {
        expect(ALERT_THRESHOLDS.hourlyQuerySpike).toBeGreaterThan(1);
    });

    it('maxDailyQueries 임계값이 0보다 커야 한다', () => {
        expect(ALERT_THRESHOLDS.maxDailyQueries).toBeGreaterThan(0);
    });
});
