import { createHash } from "node:crypto";
import { AuditOutcome, PermissionProfile, SiteEnvironment, SiteStatus, SubscriptionStatus } from "@prisma/client";
import { db } from "@/lib/db";
import { getEnv } from "@/lib/env";
import { assertPublicDnsTarget } from "./client";
import { normalizePublicSiteUrl } from "./url";
import { toEnrolledSite } from "./enrollment";
import { encryptCredential } from "./credential-crypto";
import { syncSiteLicense } from "./license-sync";

export type PairingInput = {
  organizationId: string;
  actorUserId: string;
  name: string;
  url: string;
  code: string;
  environment: "Production" | "Staging" | "Development";
  profile: "read_only" | "standard";
};

type PairingResponse = {
  success?: boolean;
  site_id?: string;
  version?: string;
  credential_id?: string;
  token?: string;
  profile?: string;
  message?: string;
};

const environmentToDb: Record<PairingInput["environment"], SiteEnvironment> = {
  Production: SiteEnvironment.PRODUCTION,
  Staging: SiteEnvironment.STAGING,
  Development: SiteEnvironment.DEVELOPMENT,
};

export async function pairSite(input: PairingInput) {
  const url = normalizePublicSiteUrl(input.url);
  await assertPublicDnsTarget(url.hostname);

  const subscription = await db.subscription.findUnique({
    where: { organizationId: input.organizationId },
    include: { plan: true },
  });
  if (!subscription || (subscription.status !== SubscriptionStatus.ACTIVE && subscription.status !== SubscriptionStatus.TRIALING)) {
    throw new Error("An active WooCommerce subscription is required before connecting a site.");
  }
  const existing = await db.site.findUnique({ where: { normalizedUrl: url.origin } });
  if (existing) throw new Error("This site is already enrolled. Manage it from Sites.");
  const usedSites = await db.site.count({ where: { organizationId: input.organizationId, status: { not: SiteStatus.REMOVED } } });
  if (usedSites >= subscription.plan.siteLimit) throw new Error("Your current subscription has reached its site limit.");

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 12_000);
  let claim: PairingResponse;
  try {
    const response = await fetch(new URL("/wp-json/site-agent/v1/pair/claim", url.origin), {
      method: "POST",
      headers: { "Content-Type": "application/json", Accept: "application/json" },
      body: JSON.stringify({ code: input.code, profile: input.profile }),
      cache: "no-store",
      redirect: "error",
      signal: controller.signal,
    });
    claim = await response.json() as PairingResponse;
    if (!response.ok || !claim.success || !claim.site_id || !claim.credential_id || !claim.token || claim.profile !== input.profile) {
      throw new Error(claim.message || "The pairing code was rejected. Start a new connection from WordPress.");
    }
  } finally {
    clearTimeout(timeout);
  }

  const fingerprint = createHash("sha256").update(claim.token).digest("hex");
  const encryptedSecret = encryptCredential(claim.token, getEnv().CREDENTIAL_ENCRYPTION_KEY);
  let site;
  try {
    site = await db.$transaction(async (tx) => {
    const created = await tx.site.create({
      data: {
        organizationId: input.organizationId,
        pluginSiteId: claim.site_id,
        displayName: input.name,
        normalizedUrl: url.origin,
        environment: environmentToDb[input.environment],
        status: SiteStatus.CONNECTED,
        pluginVersion: claim.version ?? null,
        remoteAccessEnabled: true,
        licenseActive: false,
        aiChatEntitled: false,
        lastSeenAt: new Date(),
      },
    });
    await tx.siteCredential.create({
      data: {
        siteId: created.id,
        label: `WordPress pairing ${claim.credential_id}`,
        secretFingerprint: fingerprint,
        encryptedSecret,
        permissionProfile: input.profile === "read_only" ? PermissionProfile.READ_ONLY : PermissionProfile.STANDARD,
      },
    });
    await tx.auditEvent.create({
      data: {
        organizationId: input.organizationId,
        siteId: created.id,
        actorUserId: input.actorUserId,
        action: "Connected WordPress site",
        operationGroup: "pairing",
        outcome: AuditOutcome.SUCCESS,
        riskLevel: "write",
        requestSummary: "Claimed a one-time WordPress pairing code; credential encrypted at rest.",
        details: { pluginSiteId: claim.site_id, profile: input.profile },
      },
    });
    return created;
    });
  } catch {
    let revoked = false;
    try {
      const controller = new AbortController();
      const timeout = setTimeout(() => controller.abort(), 5_000);
      try {
        const response = await fetch(new URL("/wp-json/site-agent/v1/pair/rollback", url.origin), {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ credential_id: claim.credential_id, token: claim.token }),
          cache: "no-store",
          redirect: "error",
          signal: controller.signal,
        });
        revoked = response.ok;
      } finally { clearTimeout(timeout); }
    } catch { /* WordPress may be unreachable; the administrator can revoke the labeled token. */ }
    throw new Error(revoked
      ? "The site could not be saved. The new WordPress credential was revoked; start pairing again."
      : "The site could not be saved. Revoke the AI Web Command SaaS credential in WordPress, then retry.");
  }
  try {
    const licenseValidUntil = await syncSiteLicense(site.id);
    return toEnrolledSite({ ...site, licenseActive: true, licenseValidUntil });
  } catch {
    return toEnrolledSite({ ...site, status: SiteStatus.NEEDS_ATTENTION });
  }
}
