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

export interface ReverseJournalEntryInput {
  id: string;
  reversalPeriodId?: string;
}

interface AuditEntry {
  type: string;
  from?: string;
  to?: string;
  actorId?: string;
  reversalEntryId?: string;
}

const VALID_PERIOD_STATUSES = ["OPEN", "FUTURE_ENTERABLE"];

/**
 * Function: reverseJournalEntry
 *
 * Creates a mirror journal entry with all debit and credit amounts inverted
 * from the original posted entry, and automatically posts the reversal entry.
 * The original entry transitions from POSTED to REVERSED status.
 */
export async function run(db: Transaction, input: ReverseJournalEntryInput, ctx: CommandContext) {
  const { id, reversalPeriodId } = input;
  const auditTrail: AuditEntry[] = [];

  // 1. Find journal entry
  const journalEntry = await db
    .selectFrom("JournalEntry")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();

  if (!journalEntry) {
    return err(new JournalEntryNotFoundError(id));
  }

  // 2. Validate status is POSTED
  if (journalEntry.status === "REVERSED") {
    return err(new AlreadyReversedError(id));
  }

  if (journalEntry.status !== "POSTED") {
    return err(new InvalidStatusForReversalError(id));
  }

  // 3. Fetch original journal lines
  const originalLines = await db
    .selectFrom("JournalLine")
    .selectAll()
    .where("journalEntryId", "=", id)
    .execute();

  // 4. Determine target period and validate
  const targetPeriodId = reversalPeriodId ?? journalEntry.accountingPeriodId;
  const period = await db
    .selectFrom("AccountingPeriod")
    .selectAll()
    .where("id", "=", targetPeriodId)
    .executeTakeFirst();

  if (!period || !VALID_PERIOD_STATUSES.includes(period.status)) {
    return err(new InvalidPeriodStatusError(targetPeriodId));
  }

  // 5. Create reversal journal entry (auto-posted)
  const now = new Date();
  const reversalEntry = await db
    .insertInto("JournalEntry")
    .values({
      companyId: journalEntry.companyId,
      accountingPeriodId: targetPeriodId,
      entryDate: now,
      journalType: journalEntry.journalType,
      referenceNumber: `REV-${journalEntry.referenceNumber}`,
      status: "POSTED",
      description: `Reversal of ${journalEntry.referenceNumber}`,
      sourceDocumentReference: journalEntry.sourceDocumentReference,
      adjustingEntryType: journalEntry.adjustingEntryType,
      reversalOfId: journalEntry.id,
      postedAt: now,
      createdAt: now,
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirst();

  // 6. Create mirror journal lines with inverted amounts
  const reversalLines = originalLines.map((line) => ({
    journalEntryId: reversalEntry!.id,
    accountId: line.accountId,
    debitAmount: line.creditAmount,
    creditAmount: line.debitAmount,
    description: line.description,
    currencyCode: line.currencyCode,
    exchangeRate: line.exchangeRate,
    functionalDebitAmount: line.functionalCreditAmount,
    functionalCreditAmount: line.functionalDebitAmount,
    createdAt: now,
    updatedAt: null,
  }));

  await db.insertInto("JournalLine").values(reversalLines).returningAll().execute();

  // 7. Transition original entry to REVERSED
  const updatedOriginal = await db
    .updateTable("JournalEntry")
    .set({
      status: "REVERSED",
      updatedAt: now,
    })
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirst();

  // 8. Record audit events
  auditTrail.push({
    type: "STATUS_TRANSITION",
    from: "POSTED",
    to: "REVERSED",
    actorId: ctx.actorId,
    reversalEntryId: reversalEntry!.id,
  });

  return ok({
    reversalEntry: reversalEntry!,
    originalEntry: updatedOriginal!,
    auditTrail,
  });
}
