import { createHash, randomUUID } from "node:crypto";
import type { Prisma, PrismaClient } from "@prisma/client";
import { lockActiveOrganization } from "../org/status.js";
import { LocalSecretEnvelope, type SecretEnvelopeV1 } from "../security/secretEnvelope.js";
import { probeFeishuApplication, type FeishuReadinessProbe } from "./feishuReadiness.js";

export interface FeishuApplicationCredentialInput {
  readonly appId: string;
  readonly appSecret: string;
  readonly botOpenId: string;
  readonly verificationToken?: string;
  readonly encryptKey?: string;
}

export interface RotateFeishuApplicationInput extends FeishuApplicationCredentialInput {
  readonly organizationId: string;
  readonly actorUserId: string;
}

export interface FeishuApplicationConnectionMetadata {
  readonly id: string;
  readonly appFingerprint: string;
  readonly status: "DRAFT" | "ACTIVE" | "DISABLED";
  readonly activeVersion: number | null;
  readonly keyId: string | null;
  readonly createdAt: Date;
  readonly updatedAt: Date;
}

export interface ResolvedFeishuApplication extends Required<FeishuApplicationCredentialInput> {
  readonly connectionId: string;
  readonly organizationId: string;
}

export interface FeishuSecretPayloadV1 extends Required<FeishuApplicationCredentialInput> {
  readonly schemaVersion: 1;
}

export class FeishuApplicationConnectionService {
  constructor(
    private readonly prisma: PrismaClient,
    private readonly secrets: LocalSecretEnvelope,
    private readonly readinessProbe: FeishuReadinessProbe = probeFeishuApplication,
  ) {}

  async rotateCustomerApplication(
    input: RotateFeishuApplicationInput,
  ): Promise<FeishuApplicationConnectionMetadata & { readonly created: boolean }> {
    const payload = validateFeishuApplicationCredential(input);
    const appIdentityFingerprint = fingerprintFeishuAppId(payload.appId);
    await this.prisma.$transaction(async (tx) => {
      await requireFeishuAdmin(tx, input);
      await assertAppIdentityAvailable(tx, input.organizationId, appIdentityFingerprint);
    });
    await this.readinessProbe(payload);

    try {
      return await this.prisma.$transaction(async (tx) => {
        await requireFeishuAdmin(tx, input);
        await assertAppIdentityAvailable(tx, input.organizationId, appIdentityFingerprint);
        const connection = await tx.organizationFeishuApplicationConnection.upsert({
          where: { organizationId: input.organizationId },
          update: {},
          create: {
            id: randomUUID(),
            organizationId: input.organizationId,
            appIdentityFingerprint,
            status: "DRAFT",
          },
        });
        await tx.$queryRaw`
          SELECT "id" FROM "OrganizationFeishuApplicationConnection"
          WHERE "id" = ${connection.id} FOR UPDATE
        `;
        const locked = await tx.organizationFeishuApplicationConnection.findUniqueOrThrow({
          where: { id: connection.id },
          include: {
            activeSecretVersion: true,
            secretVersions: { orderBy: { version: "desc" }, take: 1, select: { version: true } },
          },
        });
        if (locked.organizationId !== input.organizationId) {
          throw new Error("Feishu Application Connection scope changed during rotation");
        }
        if (locked.appIdentityFingerprint !== appIdentityFingerprint) {
          throw new Error("Feishu Application Connection cannot change app identity");
        }
        if (locked.activeSecretVersion !== null) {
          if (locked.activeSecretVersion.connectionId !== locked.id) {
            throw new Error("Feishu Application Connection active version scope mismatch");
          }
          decryptFeishuCredential(this.secrets, {
            organizationId: locked.organizationId,
            connectionId: locked.id,
            appIdentityFingerprint: locked.appIdentityFingerprint,
            secretVersionId: locked.activeSecretVersion.id,
            envelopeVersion: locked.activeSecretVersion.envelopeVersion,
            keyId: locked.activeSecretVersion.keyId,
            envelope: locked.activeSecretVersion.envelope,
          });
        }
        const version = (locked.secretVersions[0]?.version ?? 0) + 1;
        const secretVersionId = randomUUID();
        const envelope = this.secrets.encryptJson({
          purpose: "feishu-application",
          organizationId: input.organizationId,
          connectionId: locked.id,
          secretVersionId,
        }, payload);
        const now = new Date();
        const secretVersion = await tx.feishuApplicationCredentialVersion.create({
          data: {
            id: secretVersionId,
            connectionId: locked.id,
            version,
            envelopeVersion: envelope.version,
            keyId: envelope.keyId,
            envelope: envelope as unknown as Prisma.InputJsonValue,
            createdByUserId: input.actorUserId,
          },
        });
        if (locked.activeSecretVersion !== null) {
          await tx.feishuApplicationCredentialVersion.update({
            where: { id: locked.activeSecretVersion.id },
            data: { retiredAt: now },
          });
        }
        const activated = await tx.organizationFeishuApplicationConnection.update({
          where: { id: locked.id },
          data: {
            status: "ACTIVE",
            activeSecretVersionId: secretVersion.id,
            activatedAt: now,
            disabledAt: null,
          },
        });
        const action = version === 1
          ? "feishu_application.created"
          : locked.status === "DISABLED"
            ? "feishu_application.reactivated"
            : "feishu_application.rotated";
        await tx.auditEntry.create({
          data: {
            organizationId: input.organizationId,
            actorUserId: input.actorUserId,
            action,
            metadata: {
              connectionId: locked.id,
              appFingerprint: redactedFingerprint(appIdentityFingerprint),
              status: "ACTIVE",
              secretVersion: version,
              envelopeVersion: envelope.version,
              keyId: envelope.keyId,
            },
          },
        });
        return {
          ...metadata(activated, { version, keyId: secretVersion.keyId }),
          created: version === 1,
        };
      });
    } catch (error) {
      if (isAppFingerprintUniqueViolation(error)) {
        throw new Error("Feishu Application cannot be shared across Organizations", { cause: error });
      }
      throw error;
    }
  }

  async readMetadata(organizationId: string): Promise<FeishuApplicationConnectionMetadata | null> {
    return this.prisma.$transaction(async (tx) => {
      await lockActiveOrganization(tx, organizationId);
      const connection = await tx.organizationFeishuApplicationConnection.findUnique({
        where: { organizationId },
        include: { activeSecretVersion: { select: { version: true, keyId: true } } },
      });
      return connection === null ? null : metadata(connection, connection.activeSecretVersion);
    });
  }

  async disable(input: {
    readonly organizationId: string;
    readonly actorUserId: string;
  }): Promise<FeishuApplicationConnectionMetadata> {
    return this.prisma.$transaction(async (tx) => {
      await requireFeishuAdmin(tx, input);
      const connection = await tx.organizationFeishuApplicationConnection.findUnique({
        where: { organizationId: input.organizationId },
        select: { id: true },
      });
      if (connection === null) throw new Error("Feishu Application Connection not found");
      await tx.$queryRaw`
        SELECT "id" FROM "OrganizationFeishuApplicationConnection"
        WHERE "id" = ${connection.id} FOR UPDATE
      `;
      const locked = await tx.organizationFeishuApplicationConnection.findUniqueOrThrow({
        where: { id: connection.id },
        include: { activeSecretVersion: { select: { version: true, keyId: true } } },
      });
      if (locked.status === "DISABLED") return metadata(locked, locked.activeSecretVersion);
      const disabled = await tx.organizationFeishuApplicationConnection.update({
        where: { id: locked.id },
        data: { status: "DISABLED", disabledAt: new Date() },
      });
      await tx.auditEntry.create({
        data: {
          organizationId: input.organizationId,
          actorUserId: input.actorUserId,
          action: "feishu_application.disabled",
          metadata: {
            connectionId: locked.id,
            appFingerprint: redactedFingerprint(locked.appIdentityFingerprint),
            previousStatus: locked.status,
            status: "DISABLED",
          },
        },
      });
      return metadata(disabled, locked.activeSecretVersion);
    });
  }
}

export async function resolveActiveFeishuApplication(
  prisma: PrismaClient,
  secrets: LocalSecretEnvelope,
  scope: { readonly organizationId: string } | { readonly connectionId: string },
): Promise<ResolvedFeishuApplication> {
  return prisma.$transaction(async (tx) => {
    const connection = "organizationId" in scope
      ? await tx.organizationFeishuApplicationConnection.findUnique({
          where: { organizationId: scope.organizationId },
          include: { activeSecretVersion: true },
        })
      : await tx.organizationFeishuApplicationConnection.findUnique({
          where: { id: scope.connectionId },
          include: { activeSecretVersion: true },
        });
    if (connection === null) throw new Error("active Feishu Application Connection not found");
    await lockActiveOrganization(tx, connection.organizationId);
    const version = connection.activeSecretVersion;
    if (connection.status !== "ACTIVE" || version === null || version.retiredAt !== null ||
        version.connectionId !== connection.id) {
      throw new Error("active Feishu Application Connection not found");
    }
    const payload = decryptFeishuCredential(secrets, {
      organizationId: connection.organizationId,
      connectionId: connection.id,
      appIdentityFingerprint: connection.appIdentityFingerprint,
      secretVersionId: version.id,
      envelopeVersion: version.envelopeVersion,
      keyId: version.keyId,
      envelope: version.envelope,
    });
    return {
      connectionId: connection.id,
      organizationId: connection.organizationId,
      appId: payload.appId,
      appSecret: payload.appSecret,
      botOpenId: payload.botOpenId,
      verificationToken: payload.verificationToken,
      encryptKey: payload.encryptKey,
    };
  });
}

export function decryptFeishuCredential(
  secrets: LocalSecretEnvelope,
  input: {
    readonly organizationId: string;
    readonly connectionId: string;
    readonly appIdentityFingerprint: string;
    readonly secretVersionId: string;
    readonly envelopeVersion: number;
    readonly keyId: string;
    readonly envelope: Prisma.JsonValue;
  },
): FeishuSecretPayloadV1 {
  if (input.envelopeVersion !== 1) throw new Error("unsupported Feishu secret envelope version");
  const envelope = input.envelope as unknown as SecretEnvelopeV1;
  if (envelope.version !== input.envelopeVersion || envelope.keyId !== input.keyId) {
    throw new Error("Feishu secret envelope metadata mismatch");
  }
  const payload = secrets.decryptJson<unknown>({
    purpose: "feishu-application",
    organizationId: input.organizationId,
    connectionId: input.connectionId,
    secretVersionId: input.secretVersionId,
  }, envelope);
  const validated = validateStoredPayload(payload);
  if (fingerprintFeishuAppId(validated.appId) !== input.appIdentityFingerprint) {
    throw new Error("Feishu Application app identity fingerprint mismatch");
  }
  return validated;
}

export async function verifyStoredFeishuApplicationEnvelopes(
  prisma: PrismaClient | Prisma.TransactionClient,
  secrets: LocalSecretEnvelope,
): Promise<number> {
  const versions = await prisma.feishuApplicationCredentialVersion.findMany({
    select: {
      id: true,
      envelopeVersion: true,
      keyId: true,
      envelope: true,
      connection: { select: { id: true, organizationId: true, appIdentityFingerprint: true } },
    },
  });
  for (const version of versions) {
    decryptFeishuCredential(secrets, {
      organizationId: version.connection.organizationId,
      connectionId: version.connection.id,
      appIdentityFingerprint: version.connection.appIdentityFingerprint,
      secretVersionId: version.id,
      envelopeVersion: version.envelopeVersion,
      keyId: version.keyId,
      envelope: version.envelope,
    });
  }
  return versions.length;
}

export async function rewrapStoredFeishuApplicationEnvelopes(
  prisma: Prisma.TransactionClient,
  secrets: LocalSecretEnvelope,
): Promise<{ readonly verified: number; readonly rewrapped: number }> {
  const versions = await prisma.feishuApplicationCredentialVersion.findMany({
    select: {
      id: true,
      envelopeVersion: true,
      keyId: true,
      envelope: true,
      connection: { select: { id: true, organizationId: true, appIdentityFingerprint: true } },
    },
    orderBy: { createdAt: "asc" },
  });
  let rewrapped = 0;
  for (const version of versions) {
    const binding = {
      purpose: "feishu-application",
      organizationId: version.connection.organizationId,
      connectionId: version.connection.id,
      secretVersionId: version.id,
    } as const;
    decryptFeishuCredential(secrets, {
      organizationId: version.connection.organizationId,
      connectionId: version.connection.id,
      appIdentityFingerprint: version.connection.appIdentityFingerprint,
      secretVersionId: version.id,
      envelopeVersion: version.envelopeVersion,
      keyId: version.keyId,
      envelope: version.envelope,
    });
    if (version.keyId === secrets.activeKeyId) continue;
    const nextEnvelope = secrets.rewrap(binding, version.envelope as unknown as SecretEnvelopeV1);
    const updated = await prisma.feishuApplicationCredentialVersion.updateMany({
      where: { id: version.id, keyId: version.keyId },
      data: {
        keyId: nextEnvelope.keyId,
        envelope: nextEnvelope as unknown as Prisma.InputJsonValue,
      },
    });
    if (updated.count !== 1) {
      throw new Error(`Feishu secret envelope changed during KEK rotation: ${version.id}`);
    }
    await prisma.auditEntry.create({
      data: {
        organizationId: version.connection.organizationId,
        action: "feishu_application_secret.kek_rewrapped",
        metadata: {
          connectionId: version.connection.id,
          secretVersionId: version.id,
          previousKeyId: version.keyId,
          keyId: nextEnvelope.keyId,
        },
      },
    });
    rewrapped += 1;
  }
  const verified = await verifyStoredFeishuApplicationEnvelopes(prisma, secrets);
  const remaining = await prisma.feishuApplicationCredentialVersion.count({
    where: { keyId: { not: secrets.activeKeyId } },
  });
  if (remaining !== 0) {
    throw new Error(`KEK rotation left ${remaining} Feishu envelope(s) on a non-active key`);
  }
  return { verified, rewrapped };
}

export function validateFeishuApplicationCredential(input: FeishuApplicationCredentialInput): FeishuSecretPayloadV1 {
  const appId = nonEmpty(input.appId, "Feishu appId");
  const appSecret = nonEmpty(input.appSecret, "Feishu appSecret");
  const botOpenId = nonEmpty(input.botOpenId, "Feishu botOpenId");
  return {
    schemaVersion: 1,
    appId,
    appSecret,
    botOpenId,
    verificationToken: input.verificationToken?.trim() ?? "",
    encryptKey: input.encryptKey?.trim() ?? "",
  };
}

function validateStoredPayload(value: unknown): FeishuSecretPayloadV1 {
  if (!isRecord(value) || value["schemaVersion"] !== 1 ||
      typeof value["appId"] !== "string" || value["appId"] === "" ||
      typeof value["appSecret"] !== "string" || value["appSecret"] === "" ||
      typeof value["botOpenId"] !== "string" || value["botOpenId"] === "" ||
      typeof value["verificationToken"] !== "string" || typeof value["encryptKey"] !== "string") {
    throw new Error("invalid encrypted Feishu Application credential payload");
  }
  return value as unknown as FeishuSecretPayloadV1;
}

async function requireFeishuAdmin(
  tx: Prisma.TransactionClient,
  input: Pick<RotateFeishuApplicationInput, "organizationId" | "actorUserId">,
): Promise<void> {
  await lockActiveOrganization(tx, input.organizationId);
  const actor = await tx.organizationMembership.findFirst({
    where: {
      organizationId: input.organizationId,
      userId: input.actorUserId,
      role: { in: ["OWNER", "ADMIN"] },
      revokedAt: null,
    },
    select: { id: true },
  });
  if (actor === null) {
    throw new Error("Feishu Application Connection write requires an active Organization OWNER or ADMIN");
  }
}

async function assertAppIdentityAvailable(
  tx: Prisma.TransactionClient,
  organizationId: string,
  appIdentityFingerprint: string,
): Promise<void> {
  const organizationConnection = await tx.organizationFeishuApplicationConnection.findUnique({
    where: { organizationId },
    select: { appIdentityFingerprint: true },
  });
  if (organizationConnection !== null &&
      organizationConnection.appIdentityFingerprint !== appIdentityFingerprint) {
    throw new Error("Feishu Application Connection cannot change app identity");
  }
  const appConnection = await tx.organizationFeishuApplicationConnection.findUnique({
    where: { appIdentityFingerprint },
    select: { organizationId: true },
  });
  if (appConnection !== null && appConnection.organizationId !== organizationId) {
    throw new Error("Feishu Application cannot be shared across Organizations");
  }
}

function metadata(
  connection: {
    readonly id: string;
    readonly appIdentityFingerprint: string;
    readonly status: "DRAFT" | "ACTIVE" | "DISABLED";
    readonly createdAt: Date;
    readonly updatedAt: Date;
  },
  secret: { readonly version: number; readonly keyId: string } | null,
): FeishuApplicationConnectionMetadata {
  return {
    id: connection.id,
    appFingerprint: redactedFingerprint(connection.appIdentityFingerprint),
    status: connection.status,
    activeVersion: secret?.version ?? null,
    keyId: secret?.keyId ?? null,
    createdAt: connection.createdAt,
    updatedAt: connection.updatedAt,
  };
}

export function fingerprintFeishuAppId(appId: string): string {
  return createHash("sha256").update("cph-feishu-app\0").update(appId, "utf8").digest("hex");
}

function redactedFingerprint(fingerprint: string): string {
  return `sha256:${fingerprint.slice(0, 16)}`;
}

function isAppFingerprintUniqueViolation(error: unknown): boolean {
  if (!isRecord(error) || error["code"] !== "P2002" || !isRecord(error["meta"])) return false;
  const target = error["meta"]["target"];
  return Array.isArray(target) && target.includes("appIdentityFingerprint");
}

function nonEmpty(value: string, label: string): string {
  const normalized = value.trim();
  if (normalized === "") throw new Error(`${label} is required`);
  return normalized;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}
