import { beforeEach, describe, expect, it, vi } from "vitest";
import { SubscriptionStatus } from "@prisma/client";
import { pairSite } from "./pairing";
import { db } from "@/lib/db";

vi.mock("node:dns/promises", () => ({ lookup: vi.fn(async () => [{ address: "203.0.113.10" }]) }));
vi.mock("@/lib/env", () => ({ getEnv: () => ({ CREDENTIAL_ENCRYPTION_KEY: "test-encryption-key-material" }) }));
vi.mock("@/lib/db", () => ({ db: {
  subscription: { findUnique: vi.fn() },
  site: { findUnique: vi.fn(), count: vi.fn() },
  $transaction: vi.fn(),
} }));

const input = {
  organizationId: "org-1",
  actorUserId: "user-1",
  name: "Example site",
  url: "https://example.com",
  code: "a".repeat(64),
  environment: "Production" as const,
  profile: "read_only" as const,
};

describe("SaaS pairing", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(db.subscription.findUnique).mockResolvedValue({ status: SubscriptionStatus.ACTIVE, plan: { siteLimit: 1, allowAiChat: true } } as never);
    vi.mocked(db.site.findUnique).mockResolvedValue(null);
    vi.mocked(db.site.count).mockResolvedValue(0);
  });

  it("does not claim the WordPress code without an active subscription", async () => {
    vi.mocked(db.subscription.findUnique).mockResolvedValue(null);
    const fetchMock = vi.fn();
    vi.stubGlobal("fetch", fetchMock);
    try {
      await expect(pairSite(input)).rejects.toThrow("active WooCommerce subscription");
      expect(fetchMock).not.toHaveBeenCalled();
    } finally { vi.unstubAllGlobals(); }
  });

  it("does not claim the WordPress code after the site limit is reached", async () => {
    vi.mocked(db.site.count).mockResolvedValue(1);
    const fetchMock = vi.fn();
    vi.stubGlobal("fetch", fetchMock);
    try {
      await expect(pairSite(input)).rejects.toThrow("site limit");
      expect(fetchMock).not.toHaveBeenCalled();
    } finally { vi.unstubAllGlobals(); }
  });

  it("stores the issued token encrypted and scoped to the site", async () => {
    const createSite = vi.fn(async ({ data }) => ({ id: "site-1", ...data }));
    const createCredential = vi.fn(async () => ({ id: "credential-1" }));
    const createAudit = vi.fn(async () => ({ id: "audit-1" }));
    vi.mocked(db.$transaction).mockImplementation(async (callback) => callback({
      site: { create: createSite },
      siteCredential: { create: createCredential },
      auditEvent: { create: createAudit },
    } as never));
    const fetchMock = vi.fn(async () => new Response(JSON.stringify({
      success: true, site_id: "plugin-site-1", version: "0.14.18", credential_id: "wp-credential-1", token: "test-secret-token", profile: "read_only",
    }), { status: 200 }));
    vi.stubGlobal("fetch", fetchMock);
    try {
      const site = await pairSite(input);
      expect(site.siteId).toBe("plugin-site-1");
      const credential = createCredential.mock.calls[0][0].data;
      expect(credential.siteId).toBe("site-1");
      expect(credential.encryptedSecret).not.toContain("test-secret-token");
      expect(credential.permissionProfile).toBe("READ_ONLY");
      expect(createAudit).toHaveBeenCalledOnce();
    } finally { vi.unstubAllGlobals(); }
  });

  it("revokes the issued WordPress token when the SaaS transaction fails", async () => {
    vi.mocked(db.$transaction).mockRejectedValue(new Error("Database unavailable"));
    const fetchMock = vi.fn()
      .mockResolvedValueOnce(new Response(JSON.stringify({
        success: true, site_id: "plugin-site-1", credential_id: "wp-credential-1", token: "test-secret-token", profile: "read_only",
      }), { status: 200 }))
      .mockResolvedValueOnce(new Response(JSON.stringify({ success: true }), { status: 200 }));
    vi.stubGlobal("fetch", fetchMock);
    try {
      await expect(pairSite(input)).rejects.toThrow("credential was revoked");
      expect(fetchMock).toHaveBeenCalledTimes(2);
      expect(String(fetchMock.mock.calls[1][0])).toContain("/pair/rollback");
    } finally { vi.unstubAllGlobals(); }
  });
});
