import { logger } from "@tailor-platform/function-logger";
import type { InsufficientPermissionError } from "./errors";
import { requirePermission } from "./permissions";
import type { Result } from "./result";
import type { QueryContext, ReadonlyDB } from "./types";

export type Query<TInput, TResult> = (
  db: ReadonlyDB,
  input: TInput,
  ctx: QueryContext,
) => Promise<TResult>;

type QueryResult<TReturn> =
  TReturn extends Result<infer _V, infer _E>
    ? TReturn | Result<never, InstanceType<typeof InsufficientPermissionError>>
    : TReturn;

type Impl<TInput, TResult> = (db: ReadonlyDB, input: TInput, ctx: QueryContext) => Promise<TResult>;

// Overload: name only (no permission)
export function defineQuery<TInput, TResult>(
  name: string,
  impl: Impl<TInput, TResult>,
): Query<TInput, TResult>;
// Overload: name + permission
export function defineQuery<TInput, TResult>(
  name: string,
  permission: string,
  impl: Impl<TInput, TResult>,
): Query<TInput, QueryResult<TResult>>;
// Implementation
export function defineQuery<TInput, TResult>(
  name: string,
  ...rest: [Impl<TInput, TResult>] | [permission: string, Impl<TInput, TResult>]
  // oxlint-disable-next-line typescript/no-explicit-any -- generic return type
): Query<TInput, any> {
  if (rest.length === 2) {
    const [permission, impl] = rest;
    return async (db, input, ctx) => {
      const check = requirePermission(ctx, permission);
      if (!check.ok) {
        logger.debug(`[query:${name}] permission denied`);
        return check;
      }
      logger.debug(`[query:${name}] start`, { input });
      const start = Date.now();
      const result = await impl(db, input, ctx);
      const ms = Date.now() - start;
      logger.debug(`[query:${name}] ok (${ms}ms)`);
      return result;
    };
  }
  const [impl] = rest;
  return async (db, input, ctx) => {
    logger.debug(`[query:${name}] start`, { input });
    const start = Date.now();
    const result = await impl(db, input, ctx);
    const ms = Date.now() - start;
    logger.debug(`[query:${name}] ok (${ms}ms)`);
    return result;
  };
}
