import type { Transaction } from "../generated/kysely-tailordb";
import {
  AllocationCycleNotFoundError,
  InvalidStateError,
  NoAllocationRulesError,
  InvalidPercentageError,
  InvalidPercentageSumError,
} from "../lib/errors.generated";
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";

export interface ValidateAllocationCycleInput {
  allocationCycleId: string;
}

export async function run(
  db: Transaction,
  input: ValidateAllocationCycleInput,
  ctx: CommandContext,
) {
  void ctx;

  // Validate allocation cycle exists
  const cycle = await db
    .selectFrom("AllocationCycle")
    .where("id", "=", input.allocationCycleId)
    .selectAll()
    .forUpdate()
    .executeTakeFirst();

  if (!cycle) {
    return err(new AllocationCycleNotFoundError(input.allocationCycleId));
  }

  // Validate status is DRAFT
  if (cycle.status !== "DRAFT") {
    return err(new InvalidStateError(input.allocationCycleId));
  }

  // Fetch all rules for this cycle
  const rules = await db
    .selectFrom("AllocationRule")
    .selectAll()
    .where("allocationCycleId", "=", input.allocationCycleId)
    .execute();

  if (!rules || (rules as unknown[]).length === 0) {
    return err(new NoAllocationRulesError(input.allocationCycleId));
  }

  // Validate percentages sum to 100% per sender
  const senderSums = new Map<string, number>();
  for (const rule of rules as { senderCostCenterId: string; percentage: number }[]) {
    if (rule.percentage <= 0) {
      return err(new InvalidPercentageError(rule.senderCostCenterId));
    }

    senderSums.set(
      rule.senderCostCenterId,
      (senderSums.get(rule.senderCostCenterId) ?? 0) + rule.percentage,
    );
  }
  for (const [senderId, sum] of senderSums) {
    if (Math.abs(sum - 100) > 0.001) {
      return err(new InvalidPercentageSumError(senderId));
    }
  }

  // Transition to VALIDATED
  const allocationCycle = await db
    .updateTable("AllocationCycle")
    .set({
      status: "VALIDATED",
      updatedAt: new Date(),
    })
    .where("id", "=", input.allocationCycleId)
    .returningAll()
    .executeTakeFirst();

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