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

export interface ProcessInventoryHandoffInput {
  companyId: string;
  accountingPeriodId: string;
  sourceDocumentReference: string;
  entryDate: Date;
  debitAccountId: string;
  creditAccountId: string;
  amount: number;
  currencyCode?: string;
  exchangeRate?: number;
  description?: string;
}

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

export async function run(
  db: Transaction,
  input: ProcessInventoryHandoffInput,
  _ctx: CommandContext,
) {
  // 1. Idempotency check: look for existing entry with same sourceDocumentReference + companyId
  const existing = await db
    .selectFrom("JournalEntry")
    .selectAll()
    .where("sourceDocumentReference", "=", input.sourceDocumentReference)
    .where("companyId", "=", input.companyId)
    .executeTakeFirst();
  if (existing) return ok({ journalEntry: existing, acknowledged: true, idempotent: true });

  // 2. Validate amount > 0
  if (input.amount <= 0) return err(new InvalidAmountError(input.sourceDocumentReference));

  // 3. Find and validate period
  const period = await db
    .selectFrom("AccountingPeriod")
    .selectAll()
    .where("id", "=", input.accountingPeriodId)
    .executeTakeFirst();
  if (!period || !VALID_PERIOD_STATUSES.includes(period.status))
    return err(new InvalidPeriodStatusError(input.accountingPeriodId));

  // 4. Validate company matches period's company
  if (period.companyId !== input.companyId) return err(new CompanyMismatchError(input.companyId));

  // 5. Validate debit account is ACTIVE
  const debitAccount = await db
    .selectFrom("Account")
    .selectAll()
    .where("id", "=", input.debitAccountId)
    .executeTakeFirst();
  if (!debitAccount) return err(new AccountNotFoundError(input.debitAccountId));
  if (debitAccount.status !== "ACTIVE") return err(new AccountNotActiveError(input.debitAccountId));

  // 6. Validate credit account is ACTIVE
  const creditAccount = await db
    .selectFrom("Account")
    .selectAll()
    .where("id", "=", input.creditAccountId)
    .executeTakeFirst();
  if (!creditAccount) return err(new AccountNotFoundError(input.creditAccountId));
  if (creditAccount.status !== "ACTIVE")
    return err(new AccountNotActiveError(input.creditAccountId));

  // 7. Calculate functional amounts
  const rate = input.exchangeRate ?? 1;
  const functionalAmount = input.amount * rate;

  // 8. Create journal entry (auto-posted)
  const now = new Date();
  const journalEntry = await db
    .insertInto("JournalEntry")
    .values({
      companyId: input.companyId,
      accountingPeriodId: input.accountingPeriodId,
      entryDate: input.entryDate,
      journalType: "INVENTORY",
      referenceNumber: input.sourceDocumentReference,
      status: "POSTED",
      description: input.description ?? null,
      sourceDocumentReference: input.sourceDocumentReference,
      postedAt: now,
      createdAt: now,
    })
    .returningAll()
    .executeTakeFirst();

  // 9. Create journal lines (debit + credit)
  await db
    .insertInto("JournalLine")
    .values([
      {
        journalEntryId: journalEntry!.id,
        accountId: input.debitAccountId,
        debitAmount: input.amount,
        creditAmount: null,
        functionalDebitAmount: functionalAmount,
        functionalCreditAmount: null,
        currencyCode: input.currencyCode ?? null,
        exchangeRate: input.exchangeRate ?? null,
        createdAt: now,
      },
      {
        journalEntryId: journalEntry!.id,
        accountId: input.creditAccountId,
        debitAmount: null,
        creditAmount: input.amount,
        functionalDebitAmount: null,
        functionalCreditAmount: functionalAmount,
        currencyCode: input.currencyCode ?? null,
        exchangeRate: input.exchangeRate ?? null,
        createdAt: now,
      },
    ])
    .returningAll()
    .execute();

  return ok({
    journalEntry: journalEntry!,
    acknowledged: true,
    acknowledgedAt: now,
    idempotent: false,
  });
}
