import type { Transaction } from "../generated/kysely-tailordb";
import {
  CostElementNotFoundError,
  InvalidStateError,
  TypeChangeNotAllowedError,
  CategoryChangeNotAllowedError,
  GlAccountChangeNotAllowedError,
  InvalidCategoryForTypeError,
  GlAccountNotFoundError,
  DuplicateGlAccountReferenceError,
  InvalidValidityPeriodError,
} from "../lib/errors.generated";
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";

const PRIMARY_CATEGORIES = ["overhead", "material", "labor", "depreciation"];
const SECONDARY_CATEGORIES = [
  "assessment",
  "distribution",
  "internal_activity_allocation",
  "settlement",
];

export type UpdateCostElementInput = {
  costElementId: string;
  name?: string;
  description?: string | null;
  category?: string;
  glAccountId?: string | null;
  type?: string;
  validFrom?: Date | null;
  validTo?: Date | null;
};
/** Command: updateCostElement
 * Modifies fields on an existing cost element that is in DRAFT or ACTIVE status.
 */
export async function run(db: Transaction, input: UpdateCostElementInput, ctx: CommandContext) {
  void ctx;

  // Fetch cost element with lock
  const costElement = await db
    .selectFrom("CostElement")
    .selectAll()
    .where("id", "=", input.costElementId)
    .forUpdate()
    .executeTakeFirst();

  if (!costElement) {
    return err(new CostElementNotFoundError(input.costElementId));
  }

  // Must be DRAFT or ACTIVE
  if (costElement.status !== "DRAFT" && costElement.status !== "ACTIVE") {
    return err(new InvalidStateError(costElement.id));
  }

  // Type change is never permitted
  if (input.type !== undefined && input.type !== costElement.type) {
    return err(new TypeChangeNotAllowedError(costElement.id));
  }

  // Category change not permitted when ACTIVE
  if (
    input.category !== undefined &&
    input.category !== costElement.category &&
    costElement.status === "ACTIVE"
  ) {
    return err(new CategoryChangeNotAllowedError(costElement.id));
  }

  // GL account change not permitted when ACTIVE
  if (
    input.glAccountId !== undefined &&
    input.glAccountId !== costElement.glAccountId &&
    costElement.status === "ACTIVE"
  ) {
    return err(new GlAccountChangeNotAllowedError(costElement.id));
  }

  // Validate category matches type if changed
  const effectiveCategory = input.category ?? costElement.category;
  const effectiveType = costElement.type;
  if (input.category !== undefined && input.category !== costElement.category) {
    if (effectiveType === "PRIMARY" && !PRIMARY_CATEGORIES.includes(effectiveCategory)) {
      return err(new InvalidCategoryForTypeError(costElement.id));
    }
    if (effectiveType === "SECONDARY" && !SECONDARY_CATEGORIES.includes(effectiveCategory)) {
      return err(new InvalidCategoryForTypeError(costElement.id));
    }
  }

  // Validate GL account if changed on DRAFT PRIMARY element
  if (
    input.glAccountId !== undefined &&
    input.glAccountId !== costElement.glAccountId &&
    input.glAccountId !== null
  ) {
    // Validate GL account exists and is active in an active CoA
    const account = await db
      .selectFrom("Account" as never)
      .where("id" as never, "=", input.glAccountId as never)
      .selectAll()
      .executeTakeFirst();

    if (!account || (account as Record<string, unknown>).status !== "ACTIVE") {
      return err(new GlAccountNotFoundError(input.glAccountId));
    }

    const chartOfAccounts = await db
      .selectFrom("ChartOfAccounts" as never)
      .where("id" as never, "=", (account as Record<string, unknown>).chartOfAccountsId as never)
      .selectAll()
      .executeTakeFirst();

    if (!chartOfAccounts || (chartOfAccounts as Record<string, unknown>).status !== "ACTIVE") {
      return err(new GlAccountNotFoundError(input.glAccountId));
    }

    // Check no other PRIMARY element references same GL account in same company
    const duplicateGlRef = await db
      .selectFrom("CostElement")
      .where("companyId", "=", costElement.companyId)
      .where("glAccountId", "=", input.glAccountId)
      .where("type", "=", "PRIMARY")
      .where("id", "!=", costElement.id)
      .selectAll()
      .executeTakeFirst();

    if (duplicateGlRef) {
      return err(new DuplicateGlAccountReferenceError(input.glAccountId));
    }
  }

  // Validate validity dates
  const effectiveValidFrom =
    input.validFrom !== undefined ? input.validFrom : costElement.validFrom;
  const effectiveValidTo = input.validTo !== undefined ? input.validTo : costElement.validTo;
  if (effectiveValidFrom && effectiveValidTo && effectiveValidFrom > effectiveValidTo) {
    return err(new InvalidValidityPeriodError(costElement.id));
  }

  // Build update set
  const updateSet: Record<string, unknown> = { updatedAt: new Date() };
  if (input.name !== undefined) updateSet.name = input.name;
  if (input.description !== undefined) updateSet.description = input.description;
  if (input.category !== undefined) updateSet.category = input.category;
  if (input.glAccountId !== undefined) updateSet.glAccountId = input.glAccountId;
  if (input.validFrom !== undefined) updateSet.validFrom = input.validFrom;
  if (input.validTo !== undefined) updateSet.validTo = input.validTo;

  const updated = await db
    .updateTable("CostElement")
    .set(updateSet)
    .where("id", "=", costElement.id)
    .returningAll()
    .executeTakeFirst();

  return ok({ costElement: updated! });
}
