import { ok, err, type ReadonlyDB, type QueryContext } from "@tailor-platform/erp-kit/core";
import type { DB } from "../generated/kysely-tailordb";
import {
  InactiveUnitError,
  IncompatibleUnitsError,
  UnitNotFoundError,
} from "../lib/errors.generated";
import { getUnit } from "./getUnit.generated";

export interface ConvertQuantityInput {
  quantity: number;
  sourceUnitSymbol: string;
  targetUnitSymbol: string;
}

/**
 * Converts a quantity from one unit of measure to another within the same category.
 * Units are identified by their symbol (e.g., "kg", "lb", "g").
 * The conversion uses each unit's conversion factor relative to the category's reference unit.
 * Result is rounded to the target unit's precision setting.
 */
export async function run(db: ReadonlyDB<DB>, input: ConvertQuantityInput, ctx: QueryContext) {
  // Validate source unit exists
  const { unit: sourceUnit } = (await getUnit(db, { symbol: input.sourceUnitSymbol }, ctx)).value;

  if (!sourceUnit) {
    return err(new UnitNotFoundError(input.sourceUnitSymbol));
  }

  // Validate target unit exists
  const { unit: targetUnit } = (await getUnit(db, { symbol: input.targetUnitSymbol }, ctx)).value;

  if (!targetUnit) {
    return err(new UnitNotFoundError(input.targetUnitSymbol));
  }

  // Validate both units are active
  if (sourceUnit.status !== "ACTIVE") {
    return err(new InactiveUnitError(input.sourceUnitSymbol));
  }

  if (targetUnit.status !== "ACTIVE") {
    return err(new InactiveUnitError(input.targetUnitSymbol));
  }

  // Validate units belong to the same category
  if (sourceUnit.categoryId !== targetUnit.categoryId) {
    return err(
      new IncompatibleUnitsError(`${input.sourceUnitSymbol} and ${input.targetUnitSymbol}`),
    );
  }

  // Perform conversion: result = quantity * sourceConversionFactor / targetConversionFactor
  const rawResult = (input.quantity * sourceUnit.conversionFactor) / targetUnit.conversionFactor;

  // Apply rounding to target unit's precision
  const roundingFactor = Math.pow(10, targetUnit.roundingPrecision);
  const convertedQuantity = Math.round(rawResult * roundingFactor) / roundingFactor;

  return ok({
    convertedQuantity,
    sourceUnit,
    targetUnit,
  });
}
