import type { CaseRunRecord } from "./contracts.ts";

export type CaseRunCommand =
  | { kind: "start"; caseId: string; runId?: string }
  | { kind: "status"; runId: string }
  | { kind: "events"; runId: string }
  | { kind: "aggregate"; runId: string }
  | { kind: "cancel"; runId: string };

export type HarnessRLService = {
  submit(input: { caseId: string; runId?: string; runtimeApiUrl: string }): Promise<CaseRunRecord | null>;
  getRun(runId: string): Promise<CaseRunRecord>;
  events(runId: string): Promise<Record<string, unknown>>;
  aggregate(runId: string): Promise<Record<string, unknown>>;
  cancel(runId: string): Promise<CaseRunRecord | null>;
};

export type CaseRunTransport = {
  mode: "local" | "api";
  visibility(command: CaseRunCommand): Record<string, unknown>;
  execute(command: CaseRunCommand): Promise<Record<string, unknown>>;
  close(): Promise<void>;
};

const DEFAULT_API_TIMEOUT_MS = 30000;

export class CaseRunTransportError extends Error {
  code: string;
  details?: unknown;

  constructor(code: string, message: string, details?: unknown) {
    super(message);
    this.name = "CaseRunTransportError";
    this.code = code;
    this.details = details;
  }
}

export function createLocalCaseRunTransport(options: { service: HarnessRLService; close?: () => Promise<void> }): CaseRunTransport {
  return {
    mode: "local",
    visibility() {
      return { service: "native-harnessrl-application-service", baseUrl: null, route: null, method: null };
    },
    async execute(command) {
      try {
        if (command.kind === "start") return caseRunAccepted(await options.service.submit({ caseId: command.caseId, runId: command.runId, runtimeApiUrl: "local://harnessrl" }));
        if (command.kind === "status") return options.service.getRun(command.runId);
        if (command.kind === "events") return options.service.events(command.runId);
        if (command.kind === "aggregate") return options.service.aggregate(command.runId);
        return acceptedRecord(await options.service.cancel(command.runId));
      } catch (error) {
        throw typedError(error, "harnessrl_error");
      }
    },
    close: options.close ?? (async () => {})
  };
}

export function createApiCaseRunTransport(options: { baseUrl: string; fetchImpl?: typeof fetch; apiKey?: string; timeoutMs?: number }): CaseRunTransport {
  const baseUrl = apiOrigin(options.baseUrl);
  const fetchImpl = options.fetchImpl ?? fetch;
  const timeoutMs = apiTimeoutMs(options.timeoutMs);
  return {
    mode: "api",
    visibility(command) {
      return apiRequestVisibility(baseUrl, command);
    },
    async execute(command) {
      const request = apiRequestVisibility(baseUrl, command);
      const controller = new AbortController();
      const timer = setTimeout(() => controller.abort(), timeoutMs);
      try {
        const response = await fetchImpl(`${baseUrl}${request.route}`, {
          method: request.method,
          signal: controller.signal,
          headers: {
            ...(command.kind === "start" ? { "content-type": "application/json" } : {}),
            ...(options.apiKey ? { authorization: `Bearer ${options.apiKey}` } : {})
          },
          body: command.kind === "start" ? JSON.stringify({ caseId: command.caseId, ...(command.runId ? { runId: command.runId } : {}) }) : undefined
        });
        const body = await response.json().catch((error) => {
          if (controller.signal.aborted) throw error;
          return null;
        }) as any;
        if (!response.ok || !body || typeof body !== "object") {
          throw new CaseRunTransportError(body?.error?.code ?? "caserun_api_error", body?.error?.message ?? `CaseRun API returned HTTP ${response.status}`, body?.error?.details ?? { status: response.status });
        }
        return body;
      } catch (error) {
        if (controller.signal.aborted) {
          throw new CaseRunTransportError("caserun_api_timeout", `CaseRun API request timed out after ${timeoutMs}ms`, { timeoutMs, baseUrl, route: request.route, method: request.method });
        }
        throw typedError(error, "caserun_api_unavailable");
      } finally {
        clearTimeout(timer);
      }
    },
    close: async () => {}
  };
}

export function caseRunAccepted(record: CaseRunRecord | null) {
  if (!record) throw new CaseRunTransportError("caserun_start_failed", "CaseRun start did not return a run record");
  return {
    ok: true,
    contractVersion: "hwlab-caserun-web-v1",
    runId: record.runId,
    caseId: record.caseId,
    workflowId: record.workflowId,
    workflowRunId: record.workflowRunId,
    status: record.status,
    stage: record.stage,
    statusUrl: `/v1/caserun/runs/${encodeURIComponent(record.runId)}`,
    eventsUrl: `/v1/caserun/runs/${encodeURIComponent(record.runId)}/events`,
    aggregateUrl: `/v1/caserun/runs/${encodeURIComponent(record.runId)}/aggregate`,
    cancelUrl: `/v1/caserun/runs/${encodeURIComponent(record.runId)}/cancel`
  };
}

function acceptedRecord(record: CaseRunRecord | null) {
  if (!record) throw new CaseRunTransportError("caserun_cancel_failed", "CaseRun cancel did not return a run record");
  return record;
}

function apiOrigin(value: string) {
  const text = String(value ?? "").trim();
  if (!text) throw new CaseRunTransportError("caserun_base_url_required", "--base-url <origin> is required with --over-api");
  try {
    const url = new URL(text);
    if ((url.protocol !== "http:" && url.protocol !== "https:") || url.pathname !== "/" || url.search || url.hash) throw new Error("origin required");
    return url.origin;
  } catch {
    throw new CaseRunTransportError("invalid_caserun_base_url", "--base-url must be an absolute HTTP(S) origin", { baseUrl: text });
  }
}

function apiTimeoutMs(value: number | undefined) {
  const timeoutMs = value ?? DEFAULT_API_TIMEOUT_MS;
  if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) throw new CaseRunTransportError("invalid_caserun_timeout", "--timeout-ms must be a positive integer", { timeoutMs });
  return timeoutMs;
}

function apiRequestVisibility(baseUrl: string, command: CaseRunCommand) {
  const route = command.kind === "start" ? "/v1/caserun/runs" : `/v1/caserun/runs/${encodeURIComponent(command.runId)}${command.kind === "status" ? "" : `/${command.kind}`}`;
  const method = command.kind === "start" || command.kind === "cancel" ? "POST" : "GET";
  return { baseUrl, route, method };
}

function typedError(error: any, fallbackCode: string) {
  if (error instanceof CaseRunTransportError) return error;
  return new CaseRunTransportError(error?.code ?? fallbackCode, error?.message ?? String(error), error?.details);
}
