import { logger } from "@tailor-platform/function-logger";
import type { Transaction } from "@tailor-platform/sdk/kysely";
import type { InsufficientPermissionError } from "./errors";
import { requirePermission } from "./permissions";
import type { Result } from "./result";
import type { CommandContext } from "./types";

export type Command<TInput, TReturn extends Result<unknown, Error>> = (
  // oxlint-disable-next-line typescript/no-explicit-any -- generic Transaction type
  db: Transaction<any>,
  input: TInput,
  ctx: CommandContext,
) => Promise<TReturn>;

type CommandResult<TReturn extends Result<unknown, Error>> =
  | TReturn
  | Result<never, InstanceType<typeof InsufficientPermissionError>>;

export function defineCommand<
  TDeps extends unknown[],
  TInput,
  TReturn extends Result<unknown, Error>,
>(
  name: string,
  permission: string,
  impl: (
    // oxlint-disable-next-line typescript/no-explicit-any -- generic Transaction type
    db: Transaction<any>,
    input: TInput,
    ctx: CommandContext,
    ...deps: TDeps
  ) => Promise<TReturn>,
): (...deps: TDeps) => Command<TInput, CommandResult<TReturn>> {
  return (...deps) =>
    async (db, input, ctx) => {
      if (!db.isTransaction) {
        throw new Error("Commands require a transaction database");
      }
      const check = requirePermission(ctx, permission);
      if (!check.ok) {
        logger.debug(`[command:${name}] permission denied`);
        return check;
      }
      logger.debug(`[command:${name}] start`, { input });
      const start = Date.now();
      const result = await impl(db, input, ctx, ...deps);
      const ms = Date.now() - start;
      if ("ok" in result && !result.ok) {
        logger.debug(`[command:${name}] error (${ms}ms)`, {
          error: result.error,
        });
      } else {
        logger.debug(`[command:${name}] ok (${ms}ms)`);
      }
      return result;
    };
}
