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

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

export interface CreateAuditPolicyInput {
  entityName: string;
  companyId?: string | null;
  operationType: string;
  fieldRules?: { fieldName: string; sensitivityMode?: string }[];
}

/**
 * Function: createAuditPolicy
 *
 * Creates a new audit policy in DRAFT status. Validates the entity name,
 * scope, operation type, and optional field-level rules before inserting.
 */
export async function run(db: Transaction, input: CreateAuditPolicyInput, ctx: CommandContext) {
  const { entityName, companyId, operationType, fieldRules } = input;

  // 1. Scope authorization: company-scoped caller can only create for their company
  if (ctx.companyId !== undefined) {
    if (ctx.companyId !== null && companyId !== ctx.companyId) {
      return err(new UnauthorizedError(ctx.actorId));
    }
    if (ctx.companyId === null && companyId != null) {
      return err(new UnauthorizedError(ctx.actorId));
    }
  }

  // 2. Look up AuditableEntity by entityName
  const entity = await db
    .selectFrom("AuditableEntity")
    .where("entityName", "=", entityName)
    .selectAll()
    .executeTakeFirst();

  if (!entity) {
    return err(new EntityTypeNotRegisteredError(entityName));
  }

  // 2. Validate scope match
  if (entity.entityScope === "COMPANY_BOUND" && !companyId) {
    return err(new ScopeMismatchError(entityName));
  }
  if (entity.entityScope === "GLOBAL" && companyId) {
    return err(new ScopeMismatchError(entityName));
  }

  // 3. Validate operationType
  if (!VALID_OPERATION_TYPES.includes(operationType)) {
    return err(new InvalidOperationTypeError(operationType));
  }

  // 4. Validate field rules (if provided)
  if (fieldRules && fieldRules.length > 0) {
    // Parse auditable fields (supports both legacy string[] and new {fieldName, fieldType?}[] formats)
    const rawFields = JSON.parse(entity.auditableFields) as string[] | { fieldName: string }[];
    const auditableFieldNames = new Set<string>(
      rawFields.map((f) => (typeof f === "string" ? f : f.fieldName)),
    );
    const seenFields = new Set<string>();

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

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

      // Duplicate field names
      if (seenFields.has(rule.fieldName)) {
        return err(new DuplicateFieldNameError(rule.fieldName));
      }
      seenFields.add(rule.fieldName);

      // Invalid sensitivity mode
      if (rule.sensitivityMode && !VALID_SENSITIVITY_MODES.includes(rule.sensitivityMode)) {
        return err(new InvalidSensitivityModeError(rule.sensitivityMode));
      }
    }
  }

  // 5. Insert AuditPolicy with status=DRAFT
  const policy = await db
    .insertInto("AuditPolicy")
    .values({
      entityName,
      companyId: companyId ?? null,
      operationType,
      status: "DRAFT",
    })
    .returningAll()
    .executeTakeFirstOrThrow();

  // 6. Insert PolicyFieldRule records (if field rules provided, bulk insert)
  if (fieldRules && fieldRules.length > 0) {
    await db
      .insertInto("PolicyFieldRule")
      .values(
        fieldRules.map((rule) => ({
          auditPolicyId: policy.id,
          fieldName: rule.fieldName,
          sensitivityMode: rule.sensitivityMode ?? "CAPTURE",
        })),
      )
      .execute();
  }

  return ok({ policy });
}
