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

export interface CreateAccountGroupInput {
  chartOfAccountsId: string;
  code: string;
  name: string;
  parentAccountGroupId?: string | null;
  sortOrder?: number | null;
  numberRangeStart?: number | null;
  numberRangeEnd?: number | null;
}

/**
 * Function: createAccountGroup
 *
 * Establishes a new account group within a Chart of Accounts. Groups organize
 * GL accounts into logical categories and can be nested to form a multi-level
 * tree hierarchy.
 */
export async function run<CF extends Record<string, unknown>>(
  db: Transaction,
  input: CreateAccountGroupInput & CF,
  _ctx: CommandContext,
) {
  const {
    chartOfAccountsId,
    code,
    name,
    parentAccountGroupId,
    sortOrder,
    numberRangeStart,
    numberRangeEnd,
    ...customFields
  } = input;

  // 1. Validate CoA exists
  const coa = await db
    .selectFrom("ChartOfAccounts")
    .selectAll()
    .where("id", "=", chartOfAccountsId)
    .executeTakeFirst();

  if (!coa) {
    return err(new ChartOfAccountsNotFoundError(chartOfAccountsId));
  }

  // 2. Validate CoA is not ARCHIVED
  if (coa.status === "ARCHIVED") {
    return err(new ChartOfAccountsArchivedError(chartOfAccountsId));
  }

  // 3. Validate code is non-empty
  if (!code || code.trim() === "") {
    return err(new CodeRequiredError(code ?? ""));
  }

  // 4. Validate name is non-empty
  if (!name || name.trim() === "") {
    return err(new NameRequiredError(name ?? ""));
  }

  // 5. Validate code uniqueness within the CoA
  const existingGroup = await db
    .selectFrom("AccountGroup")
    .selectAll()
    .where("chartOfAccountsId", "=", chartOfAccountsId)
    .where("code", "=", code)
    .executeTakeFirst();

  if (existingGroup) {
    return err(new DuplicateGroupCodeError(code));
  }

  // 6. Validate parent group if provided
  if (parentAccountGroupId) {
    const parentGroup = await db
      .selectFrom("AccountGroup")
      .selectAll()
      .where("id", "=", parentAccountGroupId)
      .executeTakeFirst();

    if (!parentGroup) {
      return err(new ParentGroupNotFoundError(parentAccountGroupId));
    }

    if (parentGroup.chartOfAccountsId !== chartOfAccountsId) {
      return err(new ParentGroupNotFoundError(parentAccountGroupId));
    }
  }

  // 7. Validate number range
  const rangeStart = numberRangeStart ?? null;
  const rangeEnd = numberRangeEnd ?? null;

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

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

    // Check for overlap with existing groups in the same CoA
    const existingGroups = await db
      .selectFrom("AccountGroup")
      .selectAll()
      .where("chartOfAccountsId", "=", chartOfAccountsId)
      .execute();

    for (const group of existingGroups) {
      if (group.numberRangeStart !== null && group.numberRangeEnd !== null) {
        // Overlap: ranges overlap if one starts before the other ends and vice versa
        if (rangeStart <= group.numberRangeEnd && rangeEnd >= group.numberRangeStart) {
          return err(new InvalidNumberRangeError(`${rangeStart}-${rangeEnd}`));
        }
      }
    }
  }

  // 8. Insert the account group
  const accountGroup = await db
    .insertInto("AccountGroup")
    .values({
      ...(customFields as Record<string, unknown>),
      code,
      name,
      chartOfAccountsId,
      parentAccountGroupId: parentAccountGroupId ?? null,
      sortOrder: sortOrder ?? null,
      numberRangeStart: rangeStart,
      numberRangeEnd: rangeEnd,
      createdAt: new Date(),
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirst();

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