import type { Transaction } from "../generated/kysely-tailordb";
import {
  AccountGroupNotFoundError,
  ChartOfAccountsArchivedError,
  NameRequiredError,
  InvalidNumberRangeError,
  AccountsOutOfRangeError,
} from "../lib/errors.generated";
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";

export type UpdateAccountGroupInput = (
  | { id: string }
  | { chartOfAccountsId: string; code: string }
) & {
  name?: string;
  sortOrder?: number | null;
  numberRangeStart?: number | null;
  numberRangeEnd?: number | null;
};

/**
 * Function: updateAccountGroup
 *
 * Modifies the mutable fields (name, sort order, number range) of an existing
 * account group. Code is immutable after creation.
 */
export async function run<CF extends Record<string, unknown>>(
  db: Transaction,
  input: UpdateAccountGroupInput & Omit<Partial<CF>, "status">,
  _ctx: CommandContext,
) {
  const { name, sortOrder, numberRangeStart, numberRangeEnd, ...customFields } = input;
  delete (customFields as Record<string, unknown>).id;
  delete (customFields as Record<string, unknown>).chartOfAccountsId;
  delete (customFields as Record<string, unknown>).code;

  // 1. Find the group
  let existingQuery = db.selectFrom("AccountGroup").selectAll();
  if ("id" in input) {
    existingQuery = existingQuery.where("id", "=", (input as { id: string }).id);
  } else {
    const lookup = input as { chartOfAccountsId: string; code: string };
    existingQuery = existingQuery
      .where("chartOfAccountsId", "=", lookup.chartOfAccountsId)
      .where("code", "=", lookup.code);
  }
  const existing = await existingQuery.forUpdate().executeTakeFirst();

  if (!existing) {
    const key =
      "id" in input
        ? (input as { id: string }).id
        : `${(input as { chartOfAccountsId: string; code: string }).chartOfAccountsId}:${(input as { chartOfAccountsId: string; code: string }).code}`;
    return err(new AccountGroupNotFoundError(key));
  }

  // 2. Check parent CoA status
  const coa = await db
    .selectFrom("ChartOfAccounts")
    .selectAll()
    .where("id", "=", existing.chartOfAccountsId)
    .executeTakeFirst();

  if (coa!.status === "ARCHIVED") {
    return err(new ChartOfAccountsArchivedError(existing.chartOfAccountsId));
  }

  // 3. Validate name if provided
  if (name?.trim() === "") {
    return err(new NameRequiredError(name));
  }

  // 4. Validate number range if changed
  const newRangeStart =
    numberRangeStart !== undefined ? numberRangeStart : existing.numberRangeStart;
  const newRangeEnd = numberRangeEnd !== undefined ? numberRangeEnd : existing.numberRangeEnd;

  // Both bounds must be provided together — one-sided ranges are invalid
  if ((newRangeStart !== null) !== (newRangeEnd !== null)) {
    return err(new InvalidNumberRangeError(`${newRangeStart ?? "null"}-${newRangeEnd ?? "null"}`));
  }

  if (newRangeStart !== null && newRangeEnd !== null) {
    // Range must be valid: start < end
    if (newRangeStart >= newRangeEnd) {
      return err(new InvalidNumberRangeError(`${newRangeStart}-${newRangeEnd}`));
    }

    // Check overlap with other groups in the same CoA (excluding self)
    if (numberRangeStart !== undefined || numberRangeEnd !== undefined) {
      const existingGroups = await db
        .selectFrom("AccountGroup")
        .selectAll()
        .where("chartOfAccountsId", "=", existing.chartOfAccountsId)
        .execute();

      for (const group of existingGroups) {
        if (group.id === existing.id) continue;
        if (group.numberRangeStart !== null && group.numberRangeEnd !== null) {
          if (newRangeStart <= group.numberRangeEnd && newRangeEnd >= group.numberRangeStart) {
            return err(new InvalidNumberRangeError(`${newRangeStart}-${newRangeEnd}`));
          }
        }
      }

      // 5. Check existing accounts within the new range
      const assignedAccounts = await db
        .selectFrom("Account")
        .selectAll()
        .where("accountGroupId", "=", existing.id)
        .execute();

      for (const account of assignedAccounts) {
        const accountCode = parseInt(account.code, 10);
        if (!isNaN(accountCode)) {
          if (accountCode < newRangeStart || accountCode > newRangeEnd) {
            return err(new AccountsOutOfRangeError(existing.id));
          }
        }
      }
    }
  }

  // 6. Build update data
  const updateData: Record<string, unknown> = {
    ...customFields,
    updatedAt: new Date(),
  };

  if (name !== undefined) updateData.name = name;
  if (sortOrder !== undefined) updateData.sortOrder = sortOrder;
  if (numberRangeStart !== undefined) updateData.numberRangeStart = numberRangeStart;
  if (numberRangeEnd !== undefined) updateData.numberRangeEnd = numberRangeEnd;

  const accountGroup = await db
    .updateTable("AccountGroup")
    .set(updateData)
    .where("id", "=", existing.id)
    .returningAll()
    .executeTakeFirst();

  return ok({ accountGroup: accountGroup! });
}
