import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  CompanyNotFoundError,
  AccountingPeriodNotFoundError,
  InvalidJournalTypeError,
  DuplicateReferenceNumberError,
  InvalidEntryDateError,
} from "../lib/errors.generated";

const VALID_JOURNAL_TYPES = new Set([
  "SALES",
  "PURCHASE",
  "CASH",
  "BANK",
  "MISCELLANEOUS",
  "OPENING",
]);

export interface CreateJournalEntryInput {
  companyId: string;
  accountingPeriodId: string;
  journalType: string;
  referenceNumber: string;
  entryDate: Date;
  description?: string;
  sourceDocumentReference?: string;
}

/**
 * Function: createJournalEntry
 *
 * Establishes a new journal entry in DRAFT status within the general ledger.
 * Validates company, accounting period, journal type, reference number uniqueness,
 * and entry date before creating.
 */
export async function run(db: Transaction, input: CreateJournalEntryInput, _ctx: CommandContext) {
  const {
    companyId,
    accountingPeriodId,
    journalType,
    referenceNumber,
    entryDate,
    description,
    sourceDocumentReference,
  } = input;

  // 1. Validate company exists and is ACTIVE
  const company = await db
    .selectFrom("Company")
    .selectAll()
    .where("id", "=", companyId)
    .executeTakeFirst();

  if (company?.status !== "ACTIVE") {
    return err(new CompanyNotFoundError(companyId));
  }

  // 2. Validate accounting period exists
  const period = await db
    .selectFrom("AccountingPeriod")
    .selectAll()
    .where("id", "=", accountingPeriodId)
    .executeTakeFirst();

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

  // 3. Validate journal type
  if (!VALID_JOURNAL_TYPES.has(journalType)) {
    return err(new InvalidJournalTypeError(journalType));
  }

  // 4. Validate reference number uniqueness within company
  const existingEntry = await db
    .selectFrom("JournalEntry")
    .selectAll()
    .where("companyId", "=", companyId)
    .where("referenceNumber", "=", referenceNumber)
    .executeTakeFirst();

  if (existingEntry) {
    return err(new DuplicateReferenceNumberError(referenceNumber));
  }

  // 5. Validate entry date
  if (!(entryDate instanceof Date) || isNaN(entryDate.getTime())) {
    return err(new InvalidEntryDateError(String(entryDate)));
  }

  // 6. Create journal entry in DRAFT status
  const journalEntry = await db
    .insertInto("JournalEntry")
    .values({
      companyId,
      accountingPeriodId,
      journalType: journalType as
        | "SALES"
        | "PURCHASE"
        | "CASH"
        | "BANK"
        | "MISCELLANEOUS"
        | "OPENING"
        | "CLOSING",
      referenceNumber,
      entryDate,
      status: "DRAFT",
      description: description ?? null,
      sourceDocumentReference: sourceDocumentReference ?? null,
      adjustingEntryType: null,
      reversalOfId: null,
      postedAt: null,
      createdAt: new Date(),
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirst();

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