import { describe, it, expect, beforeEach, vi } from "vitest";
import { detectFraud, createFraudAlert } from "./fraudDetection.ts";

// Mock database
vi.mock("../db", () => ({
  getDb: vi.fn(() =>
    Promise.resolve({
      select: vi.fn().mockReturnValue({
        from: vi.fn().mockReturnValue({
          where: vi.fn().mockReturnValue({
            orderBy: vi.fn().mockResolvedValue([]),
          }),
        }),
      }),
      insert: vi.fn().mockReturnValue({
        values: vi.fn().mockResolvedValue({}),
      }),
    })
  ),
}));

describe("Fraud Detection Service", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe("detectFraud", () => {
    it("should return no fraud for clean user", async () => {
      const result = await detectFraud(123, 10);

      expect(result.isFraudulent).toBe(false);
      expect(result.alerts).toHaveLength(0);
      expect(result.riskScore).toBe(0);
    });

    it("should detect rapid betting pattern", async () => {
      const { getDb } = await import("../db");
      const now = new Date();

      // Create mock transactions for rapid betting
      const rapidBets = Array.from({ length: 12 }, (_, i) => ({
        userId: 123,
        type: "game_bet",
        currency: "GC",
        amount: "10.00",
        balanceBefore: "1000.00",
        balanceAfter: "990.00",
        referenceId: "test-game",
        referenceType: "game_config",
        description: "Bet",
        createdAt: new Date(now.getTime() - (5 - i) * 30000).toISOString(), // Within 5 minutes
      }));

      vi.mocked(getDb).mockResolvedValueOnce({
        select: vi.fn().mockReturnValue({
          from: vi.fn().mockReturnValue({
            where: vi.fn().mockReturnValue({
              orderBy: vi.fn().mockResolvedValue(rapidBets),
            }),
          }),
        }),
      } as any);

      const result = await detectFraud(123, 10);

      expect(result.riskScore).toBeGreaterThan(0);
      expect(result.alerts.some((a) => a.type === "rapid_betting")).toBe(true);
    });

    it("should detect velocity (high bet amounts)", async () => {
      const { getDb } = await import("../db");
      const now = new Date();

      // Create mock transactions for high velocity
      const highBets = Array.from({ length: 5 }, (_, i) => ({
        userId: 123,
        type: "game_bet",
        currency: "SC",
        amount: "250.00",
        balanceBefore: "5000.00",
        balanceAfter: "4750.00",
        referenceId: "test-game",
        referenceType: "game_config",
        description: "Bet",
        createdAt: new Date(now.getTime() - i * 600000).toISOString(), // Within 1 hour
      }));

      vi.mocked(getDb).mockResolvedValueOnce({
        select: vi.fn().mockReturnValue({
          from: vi.fn().mockReturnValue({
            where: vi.fn().mockReturnValue({
              orderBy: vi.fn().mockResolvedValue(highBets),
            }),
          }),
        }),
      } as any);

      const result = await detectFraud(123, 250);

      expect(result.riskScore).toBeGreaterThan(0);
      expect(result.alerts.some((a) => a.type === "velocity")).toBe(true);
    });

    it("should detect abnormal RTP", async () => {
      const { getDb } = await import("../db");

      // Create mock transactions with high win rate
      const transactions = [
        ...Array.from({ length: 25 }, (_, i) => ({
          userId: 123,
          type: "game_bet",
          currency: "GC",
          amount: "10.00",
          balanceBefore: "1000.00",
          balanceAfter: "990.00",
          referenceId: "test-game",
          referenceType: "game_config",
          description: "Bet",
          createdAt: new Date(Date.now() - i * 60000).toISOString(),
        })),
        ...Array.from({ length: 20 }, (_, i) => ({
          userId: 123,
          type: "game_win",
          currency: "GC",
          amount: "50.00",
          balanceBefore: "990.00",
          balanceAfter: "1040.00",
          referenceId: "test-game",
          referenceType: "game_config",
          description: "Win",
          createdAt: new Date(Date.now() - i * 60000).toISOString(),
        })),
      ];

      vi.mocked(getDb).mockResolvedValueOnce({
        select: vi.fn().mockReturnValue({
          from: vi.fn().mockReturnValue({
            where: vi.fn().mockReturnValue({
              orderBy: vi.fn().mockResolvedValue(transactions),
            }),
          }),
        }),
      } as any);

      const result = await detectFraud(123, 10);

      expect(result.riskScore).toBeGreaterThan(0);
      expect(result.alerts.some((a) => a.type === "abnormal_rtp")).toBe(true);
    });

    it("should detect consecutive wins pattern", async () => {
      const { getDb } = await import("../db");

      // Create mock transactions with many consecutive wins
      const transactions = Array.from({ length: 20 }, (_, i) => ({
        userId: 123,
        type: i % 2 === 0 ? "game_win" : "game_bet",
        currency: "GC",
        amount: "10.00",
        balanceBefore: "1000.00",
        balanceAfter: "1010.00",
        referenceId: "test-game",
        referenceType: "game_config",
        description: i % 2 === 0 ? "Win" : "Bet",
        createdAt: new Date(Date.now() - i * 60000).toISOString(),
      }));

      vi.mocked(getDb).mockResolvedValueOnce({
        select: vi.fn().mockReturnValue({
          from: vi.fn().mockReturnValue({
            where: vi.fn().mockReturnValue({
              orderBy: vi.fn().mockResolvedValue(transactions),
            }),
          }),
        }),
      } as any);

      const result = await detectFraud(123, 10);

      // Pattern detection may or may not trigger depending on consecutive count
      expect(result.riskScore).toBeGreaterThanOrEqual(0);
    });

    it("should cap risk score at 100", async () => {
      const { getDb } = await import("../db");
      const now = new Date();

      // Create transactions that trigger multiple alerts
      const transactions = [
        // Rapid betting
        ...Array.from({ length: 15 }, (_, i) => ({
          userId: 123,
          type: "game_bet",
          currency: "SC",
          amount: "100.00",
          balanceBefore: "5000.00",
          balanceAfter: "4900.00",
          referenceId: "test-game",
          referenceType: "game_config",
          description: "Bet",
          createdAt: new Date(now.getTime() - (5 - i) * 30000).toISOString(),
        })),
        // High wins
        ...Array.from({ length: 10 }, (_, i) => ({
          userId: 123,
          type: "game_win",
          currency: "SC",
          amount: "500.00",
          balanceBefore: "4900.00",
          balanceAfter: "5400.00",
          referenceId: "test-game",
          referenceType: "game_config",
          description: "Win",
          createdAt: new Date(now.getTime() - i * 30000).toISOString(),
        })),
      ];

      vi.mocked(getDb).mockResolvedValueOnce({
        select: vi.fn().mockReturnValue({
          from: vi.fn().mockReturnValue({
            where: vi.fn().mockReturnValue({
              orderBy: vi.fn().mockResolvedValue(transactions),
            }),
          }),
        }),
      } as any);

      const result = await detectFraud(123, 100);

      expect(result.riskScore).toBeLessThanOrEqual(100);
    });
  });

  describe("createFraudAlert", () => {
    it("should create fraud alert in database", async () => {
      const { getDb } = await import("../db");
      const mockDb = {
        insert: vi.fn().mockReturnValue({
          values: vi.fn().mockResolvedValue({}),
        }),
      };

      vi.mocked(getDb).mockResolvedValueOnce(mockDb as any);

      await createFraudAlert(123, "rapid_betting", "high", "Test alert", { test: true });

      expect(mockDb.insert).toHaveBeenCalled();
    });

    it("should handle database errors gracefully", async () => {
      const { getDb } = await import("../db");

      vi.mocked(getDb).mockResolvedValueOnce(null);

      // Should not throw
      await expect(
        createFraudAlert(123, "rapid_betting", "high", "Test alert", { test: true })
      ).rejects.toThrow("Database unavailable");
    });
  });
});
