import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  JournalEntryNotFoundError,
  InvalidStatusForLineAddError,
  AccountNotFoundError,
  AccountNotActiveError,
  InvalidDebitCreditError,
  CurrencyNotFoundError,
} from "../lib/errors.generated";

export interface AddJournalLineInput {
  journalEntryId: string;
  accountId: string;
  debitAmount: number | null;
  creditAmount: number | null;
  description: string | null;
  currencyCode: string | null;
  exchangeRate: number | null;
}

export async function run(db: Transaction, input: AddJournalLineInput, ctx: CommandContext) {
  // 1. Fetch and validate journal entry
  const entry = await db
    .selectFrom("JournalEntry")
    .selectAll()
    .where("id", "=", input.journalEntryId)
    .forUpdate()
    .executeTakeFirst();

  if (!entry) {
    return err(new JournalEntryNotFoundError(input.journalEntryId));
  }

  if (entry.status !== "DRAFT") {
    return err(new InvalidStatusForLineAddError(input.journalEntryId));
  }

  // 2. Validate GL account
  const account = await db
    .selectFrom("Account")
    .selectAll()
    .where("id", "=", input.accountId)
    .executeTakeFirst();

  if (!account) {
    return err(new AccountNotFoundError(input.accountId));
  }

  if (account.status !== "ACTIVE") {
    return err(new AccountNotActiveError(input.accountId));
  }

  // 3. Validate debit/credit
  const hasDebit = input.debitAmount !== null && input.debitAmount !== undefined;
  const hasCredit = input.creditAmount !== null && input.creditAmount !== undefined;

  if (hasDebit && hasCredit) {
    return err(new InvalidDebitCreditError(input.journalEntryId));
  }

  if (!hasDebit && !hasCredit) {
    return err(new InvalidDebitCreditError(input.journalEntryId));
  }

  if (hasDebit && input.debitAmount! <= 0) {
    return err(new InvalidDebitCreditError(input.journalEntryId));
  }

  if (hasCredit && input.creditAmount! <= 0) {
    return err(new InvalidDebitCreditError(input.journalEntryId));
  }

  // 4. Multi-currency validation
  let currencyCode = input.currencyCode;
  let exchangeRate = input.exchangeRate ?? 1.0;

  if (input.currencyCode) {
    const currency = await db
      .selectFrom("Currency")
      .selectAll()
      .where("code", "=", input.currencyCode)
      .executeTakeFirst();

    if (!currency?.isActive) {
      return err(new CurrencyNotFoundError(input.currencyCode));
    }
  } else {
    currencyCode = null;
    exchangeRate = 1.0;
  }

  // 5. Compute functional amounts
  const debitAmount = hasDebit ? input.debitAmount! : null;
  const creditAmount = hasCredit ? input.creditAmount! : null;
  const functionalDebitAmount = debitAmount !== null ? debitAmount * exchangeRate : null;
  const functionalCreditAmount = creditAmount !== null ? creditAmount * exchangeRate : null;

  // 6. Create journal line
  const journalLine = await db
    .insertInto("JournalLine")
    .values({
      journalEntryId: input.journalEntryId,
      accountId: input.accountId,
      debitAmount,
      creditAmount,
      description: input.description,
      currencyCode,
      exchangeRate: input.currencyCode ? exchangeRate : null,
      functionalDebitAmount,
      functionalCreditAmount,
      createdAt: new Date(),
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirstOrThrow();

  // Emit audit event recording the line addition, acting user, and timestamp
  void ctx;

  return ok({ journalLine });
}
