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

export type UpdateJournalLineInput = {
  id: string;
  accountId?: string;
  debitAmount?: number | null;
  creditAmount?: number | null;
  description?: string;
  currencyCode?: string | null;
  exchangeRate?: number | null;
};
export async function run(db: Transaction, input: UpdateJournalLineInput, ctx: CommandContext) {
  // 1. Fetch journal line
  const existing = await db
    .selectFrom("JournalLine")
    .selectAll()
    .where("id", "=", input.id)
    .forUpdate()
    .executeTakeFirst();

  if (!existing) {
    return err(new JournalLineNotFoundError(input.id));
  }

  // 2. Validate parent entry is DRAFT
  const entry = await db
    .selectFrom("JournalEntry")
    .selectAll()
    .where("id", "=", existing.journalEntryId)
    .executeTakeFirst();

  if (entry?.status !== "DRAFT") {
    return err(new InvalidStatusForLineUpdateError(input.id));
  }

  // 3. Validate updated account if provided
  if (input.accountId !== undefined) {
    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));
    }
  }

  // 4. Resolve effective debit/credit amounts
  const amountsProvided = input.debitAmount !== undefined || input.creditAmount !== undefined;

  const effectiveDebit = input.debitAmount !== undefined ? input.debitAmount : existing.debitAmount;
  const effectiveCredit =
    input.creditAmount !== undefined ? input.creditAmount : existing.creditAmount;

  if (amountsProvided) {
    const hasDebit = effectiveDebit !== null && effectiveDebit !== undefined;
    const hasCredit = effectiveCredit !== null && effectiveCredit !== undefined;

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

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

    if (hasDebit && effectiveDebit <= 0) {
      return err(new InvalidDebitCreditError(input.id));
    }

    if (hasCredit && effectiveCredit <= 0) {
      return err(new InvalidDebitCreditError(input.id));
    }
  }

  // 5. Validate currency if provided
  const currencyProvided = input.currencyCode !== undefined;
  const effectiveCurrencyCode = currencyProvided ? input.currencyCode : existing.currencyCode;

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

    if (!currency?.isActive) {
      return err(new CurrencyNotFoundError(input.currencyCode));
    }
  }

  // 6. Compute functional amounts
  const effectiveExchangeRate =
    input.exchangeRate !== undefined ? (input.exchangeRate ?? 1.0) : (existing.exchangeRate ?? 1.0);

  const finalDebit = amountsProvided ? effectiveDebit : existing.debitAmount;
  const finalCredit = amountsProvided ? effectiveCredit : existing.creditAmount;
  const functionalDebitAmount = finalDebit !== null ? finalDebit * effectiveExchangeRate : null;
  const functionalCreditAmount = finalCredit !== null ? finalCredit * effectiveExchangeRate : null;

  // 7. Build update set
  const updateSet: Record<string, unknown> = {
    updatedAt: new Date(),
  };

  if (input.accountId !== undefined) {
    updateSet.accountId = input.accountId;
  }
  if (input.debitAmount !== undefined) {
    updateSet.debitAmount = input.debitAmount;
  }
  if (input.creditAmount !== undefined) {
    updateSet.creditAmount = input.creditAmount;
  }
  if (input.description !== undefined) {
    updateSet.description = input.description;
  }
  if (currencyProvided) {
    updateSet.currencyCode = effectiveCurrencyCode;
  }
  if (input.exchangeRate !== undefined) {
    updateSet.exchangeRate = input.exchangeRate;
  }

  updateSet.functionalDebitAmount = functionalDebitAmount;
  updateSet.functionalCreditAmount = functionalCreditAmount;

  // 8. Update journal line
  const journalLine = await db
    .updateTable("JournalLine")
    .set(updateSet)
    .where("id", "=", input.id)
    .returningAll()
    .executeTakeFirstOrThrow();

  // Emit audit event recording previous and new field values, acting user, and timestamp
  void ctx;

  return ok({ journalLine });
}
