import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { ScanScheduler } from "../src/scheduler/ScanScheduler";
import { SubscriptionTier } from "@shieldai/types";
import prisma from "@shieldai/db";

let runId = Date.now();

describe("ScanScheduler", () => {
  let scheduler: ScanScheduler;

  beforeEach(() => {
    scheduler = new ScanScheduler();
  });

  describe("static tier configuration", () => {
    it("returns correct interval for BASIC tier", () => {
      expect(ScanScheduler.getIntervalForTier(SubscriptionTier.BASIC)).toBe(1440);
    });

    it("returns correct interval for PLUS tier", () => {
      expect(ScanScheduler.getIntervalForTier(SubscriptionTier.PLUS)).toBe(360);
    });

    it("returns correct interval for PREMIUM tier", () => {
      expect(ScanScheduler.getIntervalForTier(SubscriptionTier.PREMIUM)).toBe(60);
    });

    it("returns correct cron for BASIC tier", () => {
      expect(ScanScheduler.getCronForTier(SubscriptionTier.BASIC)).toBe("0 0 * * *");
    });

    it("returns correct cron for PLUS tier", () => {
      expect(ScanScheduler.getCronForTier(SubscriptionTier.PLUS)).toBe("0 */6 * * *");
    });

    it("returns correct cron for PREMIUM tier", () => {
      expect(ScanScheduler.getCronForTier(SubscriptionTier.PREMIUM)).toBe("0 * * * *");
    });
  });

  describe("ensureScheduleForUser", () => {
    let userId: string;

    beforeEach(async () => {
      const user = await prisma.user.create({
        data: {
          email: `scheduler-test-${runId}@shieldai.local`,
          subscriptionTier: "BASIC",
        },
      });
      userId = user.id;
      runId = Date.now();
    });

    afterEach(async () => {
      await prisma.scanSchedule.deleteMany({ where: { userId } });
      await prisma.user.delete({ where: { id: userId } });
    });

    it("creates schedule for new user", async () => {
      const result = await scheduler.ensureScheduleForUser(userId);
      expect(result.scheduled).toBe(true);
      expect(result.intervalMinutes).toBe(1440);

      const schedule = await scheduler.getSchedule(userId);
      expect(schedule).not.toBeNull();
      expect(schedule?.status).toBe("ACTIVE");
      expect(schedule?.cronExpression).toBe("0 0 * * *");
    });

    it("updates schedule on tier change", async () => {
      await scheduler.ensureScheduleForUser(userId);

      await prisma.user.update({
        where: { id: userId },
        data: { subscriptionTier: "PREMIUM" },
      });

      const result = await scheduler.ensureScheduleForUser(userId);
      expect(result.intervalMinutes).toBe(60);

      const schedule = await scheduler.getSchedule(userId);
      expect(schedule?.cronExpression).toBe("0 * * * *");
    });

    it("returns false for non-existent user", async () => {
      const result = await scheduler.ensureScheduleForUser("non-existent-id");
      expect(result.scheduled).toBe(false);
      expect(result.intervalMinutes).toBe(0);
    });
  });

  describe("schedule lifecycle", () => {
    let userId: string;

    beforeEach(async () => {
      const user = await prisma.user.create({
        data: {
          email: `lifecycle-test-${runId}@shieldai.local`,
          subscriptionTier: "PLUS",
        },
      });
      userId = user.id;
      runId = Date.now();
      await scheduler.ensureScheduleForUser(userId);
    });

    afterEach(async () => {
      await prisma.scanSchedule.deleteMany({ where: { userId } });
      await prisma.user.delete({ where: { id: userId } });
    });

    it("marks schedule as scanned and updates next scan time", async () => {
      const before = await scheduler.getSchedule(userId);
      const nextScan = await scheduler.markScanned(userId);

      const after = await scheduler.getSchedule(userId);
      expect(after?.lastScanAt).not.toBeNull();
      expect(after?.nextScanAt?.getTime()).toBeGreaterThan(nextScan.getTime() - 5000);
      expect(after?.nextScanAt).not.toEqual(before?.nextScanAt);
    });

    it("pauses schedule", async () => {
      await scheduler.pauseSchedule(userId);

      const schedule = await scheduler.getSchedule(userId);
      expect(schedule?.status).toBe("PAUSED");
    });

    it("resumes paused schedule", async () => {
      await scheduler.pauseSchedule(userId);
      await scheduler.resumeSchedule(userId);

      const schedule = await scheduler.getSchedule(userId);
      expect(schedule?.status).toBe("ACTIVE");
    });
  });

  describe("getDueSchedules", () => {
    let userId1: string;
    let userId2: string;

    beforeEach(async () => {
      const user1 = await prisma.user.create({
        data: {
          email: `due-test-1-${runId}@shieldai.local`,
          subscriptionTier: "PREMIUM",
        },
      });
      userId1 = user1.id;

      const user2 = await prisma.user.create({
        data: {
          email: `due-test-2-${runId}@shieldai.local`,
          subscriptionTier: "BASIC",
        },
      });
      userId2 = user2.id;
      runId = Date.now();

      await scheduler.ensureScheduleForUser(userId1);
      await scheduler.ensureScheduleForUser(userId2);
    });

    afterEach(async () => {
      await prisma.scanSchedule.deleteMany({ where: { userId: userId1 } });
      await prisma.scanSchedule.deleteMany({ where: { userId: userId2 } });
      await prisma.user.delete({ where: { id: userId1 } });
      await prisma.user.delete({ where: { id: userId2 } });
    });

    it("returns schedules that are due", async () => {
      const pastDate = new Date(Date.now() - 60000);
      await prisma.scanSchedule.update({
        where: { userId: userId1 },
        data: { nextScanAt: pastDate },
      });

      const due = await scheduler.getDueSchedules();
      const dueUserIds = due.map((s) => s.userId);
      expect(dueUserIds).toContain(userId1);
    });

    it("includes schedules with null nextScanAt", async () => {
      await prisma.scanSchedule.update({
        where: { userId: userId2 },
        data: { nextScanAt: null },
      });

      const due = await scheduler.getDueSchedules();
      const dueUserIds = due.map((s) => s.userId);
      expect(dueUserIds).toContain(userId2);
    });
  });
});
