import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  PeriodCloseNotFoundError,
  InvalidStatusTransitionError,
  InvalidAdjustingEntryTypeError,
  UnbalancedAdjustingEntriesError,
} from "../lib/errors.generated";

export interface AdjustingEntryLineInput {
  accountId: string;
  debitAmount: number | null;
  creditAmount: number | null;
  description: string | null;
}

export interface AdjustingEntryInput {
  adjustingEntryType: string;
  description: string | null;
  lines: AdjustingEntryLineInput[];
}

export interface PostAdjustingEntriesInput {
  periodCloseId: string;
  adjustingEntries: AdjustingEntryInput[];
}

const VALID_ADJUSTING_ENTRY_TYPES = ["ACCRUAL", "DEFERRAL", "RECLASSIFICATION"];

/**
 * Function: postAdjustingEntries
 *
 * Records adjusting entries (accruals, deferrals, reclassifications) during the
 * period-end close process and transitions the PeriodClose from SUBLEDGERS_VERIFIED
 * to ADJUSTMENTS_POSTED status.
 */
export async function run(db: Transaction, input: PostAdjustingEntriesInput, ctx: CommandContext) {
  const { periodCloseId, adjustingEntries } = input;

  // 1. Find PeriodClose record
  const periodClose = await db
    .selectFrom("PeriodClose")
    .selectAll()
    .where("id", "=", periodCloseId)
    .forUpdate()
    .executeTakeFirst();

  if (!periodClose) {
    return err(new PeriodCloseNotFoundError(periodCloseId));
  }

  // 2. Validate status is SUBLEDGERS_VERIFIED
  if (periodClose.status !== "SUBLEDGERS_VERIFIED") {
    return err(new InvalidStatusTransitionError(periodCloseId));
  }

  // 3. Validate all adjusting entry types
  for (const entry of adjustingEntries) {
    if (!VALID_ADJUSTING_ENTRY_TYPES.includes(entry.adjustingEntryType)) {
      return err(new InvalidAdjustingEntryTypeError(entry.adjustingEntryType));
    }
  }

  // 4. Validate each adjusting entry is balanced (total debits == total credits)
  for (const entry of adjustingEntries) {
    const totalDebits = entry.lines.reduce((sum, line) => sum + (line.debitAmount ?? 0), 0);
    const totalCredits = entry.lines.reduce((sum, line) => sum + (line.creditAmount ?? 0), 0);
    if (totalDebits !== totalCredits) {
      return err(new UnbalancedAdjustingEntriesError(periodCloseId));
    }
  }

  // 5. Look up accounting period to get companyId
  const accountingPeriod = await db
    .selectFrom("AccountingPeriod")
    .selectAll()
    .where("id", "=", periodClose.accountingPeriodId)
    .executeTakeFirst();

  // 6. Post adjusting entries as journal entries
  const now = new Date();
  const postedEntries = [];

  let entryIndex = 0;
  for (const entry of adjustingEntries) {
    entryIndex++;
    const journalEntry = await db
      .insertInto("JournalEntry")
      .values({
        companyId: accountingPeriod!.companyId,
        accountingPeriodId: periodClose.accountingPeriodId,
        entryDate: now,
        journalType: "MISCELLANEOUS",
        referenceNumber: `ADJ-${periodCloseId}-${entryIndex}`,
        status: "POSTED",
        description: entry.description,
        adjustingEntryType: entry.adjustingEntryType as "ACCRUAL" | "DEFERRAL" | "RECLASSIFICATION",
        postedAt: now,
        createdAt: now,
      })
      .returningAll()
      .executeTakeFirst();

    // Insert journal lines for this entry
    for (const line of entry.lines) {
      await db
        .insertInto("JournalLine")
        .values({
          journalEntryId: journalEntry!.id,
          accountId: line.accountId,
          debitAmount: line.debitAmount,
          creditAmount: line.creditAmount,
          functionalDebitAmount: line.debitAmount,
          functionalCreditAmount: line.creditAmount,
          description: line.description,
          createdAt: now,
        })
        .returningAll()
        .executeTakeFirst();
    }

    postedEntries.push(journalEntry!);
  }

  // 7. Transition PeriodClose to ADJUSTMENTS_POSTED
  const updatedPeriodClose = await db
    .updateTable("PeriodClose")
    .set({
      status: "ADJUSTMENTS_POSTED",
      completionPercentage: 50,
      updatedAt: now,
    })
    .where("id", "=", periodCloseId)
    .returningAll()
    .executeTakeFirst();

  // 8. Build audit event
  const auditEvent = {
    type: "ADJUSTING_ENTRIES_POSTED",
    actorId: ctx.actorId,
    timestamp: now,
    periodCloseId,
    from: "SUBLEDGERS_VERIFIED",
    to: "ADJUSTMENTS_POSTED",
    adjustingEntries: adjustingEntries.map((e) => ({
      adjustingEntryType: e.adjustingEntryType,
      lineCount: e.lines.length,
    })),
  };

  return ok({
    periodClose: updatedPeriodClose!,
    postedEntries,
    auditEvent,
  });
}
