import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  AccountingPeriodNotFoundError,
  InvalidPeriodStatusError,
  CloseAlreadyInProgressError,
  ConcurrentCloseConflictError,
} from "../lib/errors.generated";

export interface InitiatePeriodCloseInput {
  accountingPeriodId: string;
}

const TERMINAL_PERIOD_CLOSE_STATUSES = ["PERMANENTLY_CLOSED"];

/**
 * Function: initiatePeriodClose
 *
 * Creates a new PeriodClose record in NOT_STARTED status for a given
 * accounting period. Only periods in OPEN status can have a close initiated,
 * and each period can have at most one active PeriodClose record.
 */
export async function run(db: Transaction, input: InitiatePeriodCloseInput, ctx: CommandContext) {
  const { accountingPeriodId } = input;

  // 1. Find accounting period
  const period = await db
    .selectFrom("AccountingPeriod")
    .selectAll()
    .where("id", "=", accountingPeriodId)
    .forUpdate()
    .executeTakeFirst();

  if (!period) {
    return err(new AccountingPeriodNotFoundError(accountingPeriodId));
  }

  // 2. Validate period is in OPEN status
  if (period.status !== "OPEN") {
    return err(new InvalidPeriodStatusError(accountingPeriodId));
  }

  // 3. Check for existing active PeriodClose record
  const existingClose = await db
    .selectFrom("PeriodClose")
    .selectAll()
    .where("accountingPeriodId", "=", accountingPeriodId)
    .forUpdate()
    .executeTakeFirst();

  if (existingClose && !TERMINAL_PERIOD_CLOSE_STATUSES.includes(existingClose.status)) {
    return err(new CloseAlreadyInProgressError(accountingPeriodId));
  }

  // 4. Create PeriodClose record in NOT_STARTED status
  const now = new Date();
  let periodClose;
  try {
    periodClose = await db
      .insertInto("PeriodClose")
      .values({
        accountingPeriodId,
        status: "NOT_STARTED",
        initiatedByUserId: ctx.actorId,
        completionPercentage: 0,
        yearEndCloseExecuted: false,
        createdAt: now,
      })
      .returningAll()
      .executeTakeFirstOrThrow();
  } catch {
    return err(new ConcurrentCloseConflictError(accountingPeriodId));
  }

  // 5. Build audit event
  const auditEvent = {
    type: "PERIOD_CLOSE_INITIATED",
    actorId: ctx.actorId,
    timestamp: now,
    accountingPeriodId,
  };

  return ok({ periodClose, auditEvent });
}
