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

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

const VALID_PERIOD_STATUSES = ["OPEN"];

/**
 * 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 remains POSTED so its journal lines stay in the ledger;
 * the reversal relationship is represented by the reversal entry's reversalOfId.
 */
export async function run(db: Transaction, input: ReverseJournalEntryInput, _ctx: CommandContext) {
  const { id, reversalPeriodId } = input;

  // 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 !== "POSTED") {
    return err(new InvalidStatusForReversalError(id));
  }

  // 3. Prevent duplicate reversals
  const existingReversal = await db
    .selectFrom("JournalEntry")
    .selectAll()
    .where("reversalOfId", "=", id)
    .executeTakeFirst();

  if (existingReversal) {
    return err(new AlreadyReversedError(id));
  }

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

  // 5. 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));
  }

  // 6. Create reversal journal entry (auto-posted)
  const now = new Date();
  const reversalEntry = await db
    .insertInto("JournalEntry")
    .values({
      companyId: journalEntry.companyId,
      accountingPeriodId: targetPeriodId,
      entryDate: now,
      status: "POSTED",
      description: `Reversal of journal entry ${journalEntry.id}`,
      sourceDocumentType: journalEntry.sourceDocumentType,
      sourceDocumentId: journalEntry.sourceDocumentId,
      reversalOfId: journalEntry.id,
      postedAt: now,
    })
    .returningAll()
    .executeTakeFirstOrThrow();

  // 7. 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,
  }));

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

  return ok({
    reversalEntry,
    reversalJournalLines,
    originalEntry: journalEntry,
  });
}
