/**
 * @agentguard/sdk — Main client class.
 *
 * Thin HTTP wrapper around the AgentGuard REST API.
 * ~100 lines of logic — all the intelligence lives in the Python engine.
 */

import {
  AgentGuardAPIError,
  AgentGuardConnectionError,
  AgentGuardTimeoutError,
} from "./errors.js";
import { streamSSE } from "./streaming.js";
import type {
  AgentGuardConfig,
  ArchetypeDetail,
  ArchetypeSummary,
  ChallengeRequest,
  ChallengeResult,
  GenerateRequest,
  GenerateResult,
  HealthResponse,
  SSEEvent,
  TraceDetail,
  TraceListItem,
  ValidateRequest,
  ValidationReport,
} from "./types.js";

const DEFAULT_URL = "http://localhost:8420";
const DEFAULT_TIMEOUT = 300_000; // 5 minutes

/**
 * AgentGuard SDK client.
 *
 * @example
 * ```ts
 * const ag = new AgentGuard({ url: "http://localhost:8420" });
 * const result = await ag.generate({ spec: "A REST API with auth" });
 * console.log(result.files);
 * ```
 */
export class AgentGuard {
  private readonly baseUrl: string;
  private readonly apiKey: string | undefined;
  private readonly timeout: number;
  private readonly _fetch: typeof globalThis.fetch;

  constructor(config: AgentGuardConfig = {}) {
    this.baseUrl = (config.url ?? DEFAULT_URL).replace(/\/+$/, "");
    this.apiKey = config.apiKey;
    this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
    this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
  }

  // ── Core methods ────────────────────────────────────────────────

  /** Run the full top-down generation pipeline. */
  async generate(request: GenerateRequest): Promise<GenerateResult> {
    return this.post<GenerateResult>("/v1/generate", request);
  }

  /** Stream generation progress level-by-level via SSE. */
  async *generateStream(request: GenerateRequest): AsyncGenerator<SSEEvent, void, undefined> {
    const response = await this.rawRequest("POST", "/v1/generate/stream", request);
    yield* streamSSE(response);
  }

  /** Run structural validation on code. */
  async validate(request: ValidateRequest): Promise<ValidationReport> {
    return this.post<ValidationReport>("/v1/validate", request);
  }

  /** Run self-challenge evaluation on code. */
  async challenge(request: ChallengeRequest): Promise<ChallengeResult> {
    return this.post<ChallengeResult>("/v1/challenge", request);
  }

  // ── Individual generation levels ────────────────────────────────

  /** Run L1 skeleton generation only. */
  async skeleton(request: GenerateRequest): Promise<{ files: Array<{ path: string; description: string }> }> {
    return this.post("/v1/skeleton", request);
  }

  /** Run L1 + L2 (skeleton → contracts). */
  async contracts(request: GenerateRequest): Promise<{ files: Record<string, string> }> {
    return this.post("/v1/contracts", request);
  }

  /** Run L1 → L2 → L3 (through wiring). */
  async wiring(request: GenerateRequest): Promise<{ files: Record<string, string> }> {
    return this.post("/v1/wiring", request);
  }

  /** Run L1 → L2 → L3 → L4 (through logic). */
  async logic(request: GenerateRequest): Promise<{ files: Record<string, string> }> {
    return this.post("/v1/logic", request);
  }

  // ── Archetypes ──────────────────────────────────────────────────

  /** List all available archetypes. */
  async listArchetypes(): Promise<ArchetypeSummary[]> {
    return this.get<ArchetypeSummary[]>("/v1/archetypes");
  }

  /** Get detailed archetype configuration. */
  async getArchetype(id: string): Promise<ArchetypeDetail> {
    return this.get<ArchetypeDetail>(`/v1/archetypes/${encodeURIComponent(id)}`);
  }

  // ── Traces ──────────────────────────────────────────────────────

  /** List recent traces. */
  async listTraces(): Promise<TraceListItem[]> {
    return this.get<TraceListItem[]>("/v1/traces");
  }

  /** Get a specific trace by ID. */
  async getTrace(id: string): Promise<TraceDetail> {
    return this.get<TraceDetail>(`/v1/traces/${encodeURIComponent(id)}`);
  }

  // ── Health ──────────────────────────────────────────────────────

  /** Health check. */
  async health(): Promise<HealthResponse> {
    return this.get<HealthResponse>("/health");
  }

  // ── HTTP internals ─────────────────────────────────────────────

  private headers(): Record<string, string> {
    const h: Record<string, string> = {
      "Content-Type": "application/json",
      Accept: "application/json",
    };
    if (this.apiKey) {
      h["X-Api-Key"] = this.apiKey;
    }
    return h;
  }

  private async rawRequest(method: string, path: string, body?: unknown): Promise<Response> {
    const url = `${this.baseUrl}${path}`;
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), this.timeout);

    try {
      const response = await this._fetch(url, {
        method,
        headers: this.headers(),
        body: body ? JSON.stringify(body) : undefined,
        signal: controller.signal,
      });
      return response;
    } catch (error: unknown) {
      if (error instanceof DOMException && error.name === "AbortError") {
        throw new AgentGuardTimeoutError(this.timeout);
      }
      throw new AgentGuardConnectionError(url, error instanceof Error ? error : undefined);
    } finally {
      clearTimeout(timer);
    }
  }

  private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
    const response = await this.rawRequest(method, path, body);

    if (!response.ok) {
      let detail = null;
      try {
        detail = await response.json();
      } catch {
        // ignore parse errors
      }
      throw new AgentGuardAPIError(
        response.status,
        `HTTP ${response.status}: ${detail?.detail ?? response.statusText}`,
        detail,
      );
    }

    return (await response.json()) as T;
  }

  private async get<T>(path: string): Promise<T> {
    return this.request<T>("GET", path);
  }

  private async post<T>(path: string, body: unknown): Promise<T> {
    return this.request<T>("POST", path, body);
  }
}
