import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core";
import type { Transaction, Updateable } from "../generated/kysely-tailordb";
import {
  PolicyNotFoundError,
  InvalidStateError,
  InvalidOperationTypeError,
  InvalidFieldNameError,
  DuplicateFieldNameError,
  InvalidSensitivityModeError,
  UnauthorizedError,
} from "../lib/errors.generated";

export type UpdateAuditPolicyInput = {
  policyId: string;
  operationType?: string;
  fieldRules?: { fieldName: string; sensitivityMode?: string }[];
};

const VALID_OPERATION_TYPES = ["CREATE", "UPDATE", "DELETE"] as const;
const VALID_SENSITIVITY_MODES = ["CAPTURE", "MASK", "HASH", "EXCLUDE"] as const;

/**
 * Function: updateAuditPolicy
 *
 * Updates a DRAFT audit policy's operation type and/or field-level rules.
 * Only policies in DRAFT status can be updated.
 */
export async function run(db: Transaction, input: UpdateAuditPolicyInput, ctx: CommandContext) {
  const { policyId, operationType, fieldRules } = input;

  // 1. Find policy by ID with forUpdate
  const existing = await db
    .selectFrom("AuditPolicy")
    .selectAll()
    .where("id", "=", policyId)
    .forUpdate()
    .executeTakeFirst();

  if (!existing) {
    return err(new PolicyNotFoundError(policyId));
  }

  // Scope authorization checks
  if (ctx.companyId !== undefined) {
    // Company-scoped caller cannot update policy for a different company
    if (ctx.companyId !== null && existing.companyId !== ctx.companyId) {
      return err(new UnauthorizedError(ctx.actorId));
    }
    // Global-scoped caller (companyId: null) cannot update company-scoped policy
    if (ctx.companyId === null && existing.companyId !== null) {
      return err(new UnauthorizedError(ctx.actorId));
    }
  }

  // 2. Not DRAFT → InvalidStateError
  if (existing.status !== "DRAFT") {
    return err(new InvalidStateError(policyId));
  }

  // 3. Validate operationType if provided
  if (
    operationType !== undefined &&
    !VALID_OPERATION_TYPES.includes(operationType as (typeof VALID_OPERATION_TYPES)[number])
  ) {
    return err(new InvalidOperationTypeError(operationType));
  }

  // 4. Validate fieldRules if provided
  if (fieldRules !== undefined) {
    // Look up entity's auditableFields
    const entity = await db
      .selectFrom("AuditableEntity")
      .selectAll()
      .where("entityName", "=", existing.entityName)
      .executeTakeFirst();

    // Parse auditable fields (supports both legacy string[] and new {fieldName, fieldType?}[] formats)
    const rawFields = entity
      ? (JSON.parse(entity.auditableFields) as string[] | { fieldName: string }[])
      : [];
    const auditableFieldNames = new Set<string>(
      rawFields.map((f) => (typeof f === "string" ? f : f.fieldName)),
    );

    // Check for duplicates first
    const seen = new Set<string>();
    for (const rule of fieldRules) {
      if (seen.has(rule.fieldName)) {
        return err(new DuplicateFieldNameError(rule.fieldName));
      }
      seen.add(rule.fieldName);
    }

    for (const rule of fieldRules) {
      // Empty fieldName
      if (!rule.fieldName || rule.fieldName.trim() === "") {
        return err(new InvalidFieldNameError(String(rule.fieldName)));
      }

      // Field not in auditableFields
      if (!auditableFieldNames.has(rule.fieldName)) {
        return err(new InvalidFieldNameError(rule.fieldName));
      }

      // Invalid sensitivityMode
      if (
        rule.sensitivityMode !== undefined &&
        !VALID_SENSITIVITY_MODES.includes(
          rule.sensitivityMode as (typeof VALID_SENSITIVITY_MODES)[number],
        )
      ) {
        return err(new InvalidSensitivityModeError(rule.sensitivityMode));
      }
    }
  }

  // 5. Update AuditPolicy
  const updateValues: Updateable<"AuditPolicy"> = {};
  if (operationType !== undefined) {
    updateValues.operationType = operationType;
  }

  const updatedPolicy =
    Object.keys(updateValues).length === 0
      ? existing
      : await db
          .updateTable("AuditPolicy")
          .set(updateValues)
          .where("id", "=", policyId)
          .returningAll()
          .executeTakeFirstOrThrow();

  // 6. If fieldRules provided: delete existing and bulk insert new
  if (fieldRules !== undefined) {
    await db.deleteFrom("PolicyFieldRule").where("auditPolicyId", "=", policyId).execute();

    if (fieldRules.length > 0) {
      await db
        .insertInto("PolicyFieldRule")
        .values(
          fieldRules.map((rule) => ({
            auditPolicyId: policyId,
            fieldName: rule.fieldName,
            sensitivityMode: rule.sensitivityMode ?? "CAPTURE",
          })),
        )
        .execute();
    }
  }

  return ok({ policy: updatedPolicy });
}
