import { createHash } from "node:crypto";
import type { Prisma, PrismaClient } from "@prisma/client";
import { lockActiveOrganization } from "../org/status.js";

export type FeishuPrincipalKind = "USER" | "CHAT" | "DEPARTMENT" | "USER_GROUP" | "EVENT" | "CALLBACK";

export interface ScopedFeishuIdentity {
  readonly identityId: string;
  readonly connectionId: string;
  readonly organizationId: string;
  readonly userId: string;
  readonly openId: string;
  readonly unionId: string | null;
  readonly principalId: string;
  readonly displayName: string;
  readonly avatarUrl: string | null;
}

export function scopedFeishuPrincipalId(
  kind: FeishuPrincipalKind,
  connectionId: string,
  providerLocalId: string,
): string {
  const normalizedConnectionId = nonEmpty(connectionId, "Feishu connectionId");
  const normalizedProviderLocalId = nonEmpty(providerLocalId, `Feishu ${kind} identifier`);
  const digest = createHash("sha256")
    .update("cph-feishu-principal\0")
    .update(kind, "utf8")
    .update("\0")
    .update(normalizedConnectionId, "utf8")
    .update("\0")
    .update(normalizedProviderLocalId, "utf8")
    .digest("base64url");
  return `feishu:${kind.toLowerCase()}:${normalizedConnectionId}:${digest}`;
}

export async function upsertScopedFeishuIdentity(
  prisma: PrismaClient,
  input: {
    readonly connectionId: string;
    readonly openId: string;
    readonly unionId?: string | undefined;
    readonly displayName: string;
    readonly avatarUrl?: string | undefined;
  },
): Promise<ScopedFeishuIdentity> {
  return prisma.$transaction((tx) => upsertScopedFeishuIdentityInTransaction(tx, input));
}

export async function upsertScopedFeishuIdentityInTransaction(
  prisma: Prisma.TransactionClient,
  input: {
    readonly connectionId: string;
    readonly expectedOrganizationId?: string | undefined;
    readonly openId: string;
    readonly unionId?: string | undefined;
    readonly displayName: string;
    readonly avatarUrl?: string | undefined;
  },
): Promise<ScopedFeishuIdentity> {
  const connectionId = nonEmpty(input.connectionId, "Feishu connectionId");
  const openId = nonEmpty(input.openId, "Feishu openId");
  const displayName = nonEmpty(input.displayName, "Feishu displayName");
  const unionId = optionalNonEmpty(input.unionId, "Feishu unionId");
  const avatarUrl = optionalNonEmpty(input.avatarUrl, "Feishu avatarUrl");
  const principalId = scopedFeishuPrincipalId("USER", connectionId, openId);
  const userId = deterministicFeishuUserId(connectionId, openId);

  const connection = await requireActiveFeishuApplicationConnectionInTransaction(prisma, connectionId);
  if (input.expectedOrganizationId !== undefined &&
      input.expectedOrganizationId !== connection.organizationId) {
    throw new Error("Feishu identity Organization scope mismatch");
  }
  const identity = await prisma.feishuUserIdentity.upsert({
      where: { connectionId_openId: { connectionId, openId } },
      update: {
        ...(unionId !== undefined ? { unionId } : {}),
        user: {
          update: {
            displayName,
            ...(avatarUrl !== undefined ? { avatarUrl } : {}),
          },
        },
      },
      create: {
        openId,
        ...(unionId !== undefined ? { unionId } : {}),
        connection: { connect: { id: connectionId } },
        user: {
          connectOrCreate: {
            where: { id: userId },
            create: {
              id: userId,
              feishuOpenId: principalId,
              displayName,
              ...(avatarUrl !== undefined ? { avatarUrl } : {}),
            },
          },
        },
      },
      include: { user: true },
  });
  return scopedIdentity(connection.organizationId, identity);
}

export async function resolveScopedFeishuIdentity(
  prisma: PrismaClient,
  input: {
    readonly connectionId: string;
    readonly openId: string;
    readonly expectedOrganizationId?: string | undefined;
  },
): Promise<ScopedFeishuIdentity | null> {
  const connectionId = nonEmpty(input.connectionId, "Feishu connectionId");
  const openId = nonEmpty(input.openId, "Feishu openId");
  return prisma.$transaction(async (tx) => {
    const connection = await requireActiveFeishuApplicationConnectionInTransaction(tx, connectionId);
    if (input.expectedOrganizationId !== undefined &&
        input.expectedOrganizationId !== connection.organizationId) {
      throw new Error("Feishu identity Organization scope mismatch");
    }
    const identity = await tx.feishuUserIdentity.findUnique({
      where: { connectionId_openId: { connectionId, openId } },
      include: { user: true },
    });
    return identity === null ? null : scopedIdentity(connection.organizationId, identity);
  });
}

/**
 * Lock and prove that a Feishu application connection is currently usable.
 * Callers handling provider-originated data use this before trusting a
 * connection-scoped identifier.
 */
export async function requireActiveFeishuApplicationConnectionInTransaction(
  prisma: Prisma.TransactionClient,
  connectionId: string,
): Promise<{ readonly organizationId: string }> {
  const scope = await prisma.organizationFeishuApplicationConnection.findUnique({
    where: { id: connectionId },
    select: { organizationId: true },
  });
  if (scope === null) throw new Error("active Feishu Application Connection not found");
  await lockActiveOrganization(prisma, scope.organizationId);
  await prisma.$queryRaw`
    SELECT "id" FROM "OrganizationFeishuApplicationConnection"
    WHERE "id" = ${connectionId} FOR SHARE
  `;
  const connection = await prisma.organizationFeishuApplicationConnection.findUnique({
    where: { id: connectionId },
    select: {
      organizationId: true,
      status: true,
      activeSecretVersion: { select: { connectionId: true, retiredAt: true } },
    },
  });
  if (connection === null || connection.status !== "ACTIVE" || connection.activeSecretVersion === null ||
      connection.organizationId !== scope.organizationId ||
      connection.activeSecretVersion.connectionId !== connectionId ||
      connection.activeSecretVersion.retiredAt !== null) {
    throw new Error("active Feishu Application Connection not found");
  }
  return { organizationId: connection.organizationId };
}

function scopedIdentity(
  organizationId: string,
  identity: {
    readonly id: string;
    readonly connectionId: string;
    readonly userId: string;
    readonly openId: string;
    readonly unionId: string | null;
    readonly user: {
      readonly displayName: string;
      readonly avatarUrl: string | null;
    };
  },
): ScopedFeishuIdentity {
  return {
    identityId: identity.id,
    connectionId: identity.connectionId,
    organizationId,
    userId: identity.userId,
    openId: identity.openId,
    unionId: identity.unionId,
    principalId: scopedFeishuPrincipalId("USER", identity.connectionId, identity.openId),
    displayName: identity.user.displayName,
    avatarUrl: identity.user.avatarUrl,
  };
}

export function deterministicFeishuUserId(connectionId: string, openId: string): string {
  const digest = createHash("sha256")
    .update("cph-feishu-user\0")
    .update(connectionId, "utf8")
    .update("\0")
    .update(openId, "utf8")
    .digest("base64url");
  return `feishu_${digest}`;
}

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

function optionalNonEmpty(value: string | undefined, label: string): string | undefined {
  if (value === undefined) return undefined;
  return nonEmpty(value, label);
}
