import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { WebhookHandler } from "../src/webhooks/WebhookHandler";
import prisma from "@shieldai/db";

const TEST_SECRET = "test-webhook-secret-2026";
let runId = Date.now();

describe("WebhookHandler", () => {
  let handler: WebhookHandler;

  beforeEach(() => {
    handler = new WebhookHandler(TEST_SECRET);
  });

  describe("signature verification", () => {
    it("verifies valid signature", () => {
      const payload = JSON.stringify({ userId: "test-123" });
      const sig = handler["computeSignature"](payload);
      expect(handler.verifySignature(payload, sig)).toBe(true);
    });

    it("rejects invalid signature", () => {
      const payload = JSON.stringify({ userId: "test-123" });
      expect(handler.verifySignature(payload, "invalid-sig")).toBe(false);
    });

    it("rejects missing signature", () => {
      expect(handler.verifySignature("payload", "")).toBe(false);
    });

    it("accepts signature from array", () => {
      const payload = JSON.stringify({ userId: "test-123" });
      const sig = handler["computeSignature"](payload);
      expect(handler.verifySignature(payload, ["other", sig, "another"])).toBe(true);
    });
  });

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

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

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

    it("processes SCAN_TRIGGER event", async () => {
      const result = await handler.processEvent("SCAN_TRIGGER", {
        userId,
        dataSource: "HIBP",
      });

      expect(result.eventId).toBeDefined();
      expect(result.scanTriggered).toBe(true);

      const job = await prisma.scanJob.findFirst({
        where: { userId, scheduledBy: "webhook" },
      });
      expect(job).not.toBeNull();
    });

    it("processes BREACH_DETECTED event", async () => {
      const result = await handler.processEvent("BREACH_DETECTED", {
        userId,
        breachName: "TestBreach",
      });

      expect(result.eventId).toBeDefined();
      expect(result.scanTriggered).toBe(false);
    });

    it("normalizes event type", async () => {
      const result = await handler.processEvent("scan_trigger", {
        userId,
      });

      expect(result.eventId).toBeDefined();

      const event = await prisma.webhookEvent.findUnique({
        where: { id: result.eventId },
      });
      expect(event?.eventType).toBe("SCAN_TRIGGER");
    });

    it("returns false for non-existent user", async () => {
      const result = await handler.processEvent("SCAN_TRIGGER", {
        userId: "non-existent-user-id",
      });

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

    it("links scan job to webhook event", async () => {
      const result = await handler.processEvent("SCAN_TRIGGER", {
        userId,
      });

      expect(result.scanTriggered).toBe(true);

      const event = await prisma.webhookEvent.findUnique({
        where: { id: result.eventId },
      });

      expect(event?.scanJobId).toBeDefined();
      expect(event?.processed).toBe(true);
    });
  });

  describe("signature validation in processEvent", () => {
    it("accepts event with valid signature", async () => {
      const payload = { userId: "test" };
      const payloadStr = JSON.stringify(payload);
      const sig = handler["computeSignature"](payloadStr);

      const result = await handler.processEvent("SCAN_TRIGGER", payload, undefined, sig);
      expect(result.eventId).toBeDefined();
    });

    it("rejects event with invalid signature", async () => {
      const payload = { userId: "test" };

      try {
        await handler.processEvent("SCAN_TRIGGER", payload, undefined, "bad-signature");
        expect(true).toBe(false);
      } catch (err) {
        expect((err as Error).message).toContain("signature");
      }
    });

    it("accepts event without signature when no signature provided", async () => {
      const result = await handler.processEvent("SCAN_TRIGGER", { userId: "test" });
      expect(result.eventId).toBeDefined();
    });
  });

  describe("processPendingEvents", () => {
    it("retries unprocessed events", async () => {
      const user = await prisma.user.create({
        data: {
          email: `retry-test-${runId}@shieldai.local`,
          subscriptionTier: "BASIC",
        },
      });
      runId = Date.now();

      await prisma.webhookEvent.create({
        data: {
          eventType: "SCAN_TRIGGER",
          payload: JSON.stringify({ userId: user.id }),
          processed: false,
        },
      });

      const processed = await handler.processPendingEvents();
      expect(processed).toBeGreaterThanOrEqual(1);

      const job = await prisma.scanJob.findFirst({
        where: { userId: user.id, scheduledBy: "webhook" },
      });
      expect(job).not.toBeNull();

      await prisma.scanJob.deleteMany({ where: { userId: user.id } });
      await prisma.user.delete({ where: { id: user.id } });
    });
  });

  describe("getEventHistory", () => {
    afterEach(async () => {
      await prisma.webhookEvent.deleteMany();
    });

    it("returns events ordered by creation time", async () => {
      await handler.processEvent("SCAN_TRIGGER", { userId: "user-1" });
      await handler.processEvent("BREACH_DETECTED", { userId: "user-2" });

      const events = await handler.getEventHistory();
      expect(events.length).toBeGreaterThanOrEqual(2);
      expect(events[0].createdAt.getTime()).toBeGreaterThanOrEqual(events[1].createdAt.getTime());
    });

    it("respects limit and offset", async () => {
      for (let i = 0; i < 5; i++) {
        await handler.processEvent("SCAN_TRIGGER", { userId: `user-${i}` });
      }

      const events = await handler.getEventHistory(3, 0);
      expect(events).toHaveLength(3);
    });
  });
});
