/**
 * PULSE Brain REST API client.
 *
 * All brain operations go through the local PULSE API server.
 * Localhost bypass means no auth token needed for local usage.
 */

export interface BrainSearchResult {
  query: string;
  total_results: number;
  elapsed_ms: number;
  results: Record<string, BrainResultItem[]>;
}

export interface BrainResultItem {
  text: string;
  title?: string;
  salience?: number;
  confidence?: number;
  timestamp?: string;
  source?: string;
}

export interface CaptureResult {
  status: string;
  wants_stored?: number;
  dont_wants_stored?: number;
  session_items?: number;
}

export interface SaveResult {
  status: string;
  learnings_saved?: number;
  failures_saved?: number;
}

export interface BriefingResult {
  query: string;
  briefing: string;
  empty: boolean;
}

/** Validate baseUrl is localhost-only to prevent SSRF. */
function validateBaseUrl(url: string): string {
  const parsed = new URL(url);
  if (parsed.protocol !== "http:") {
    throw new Error(`PULSE baseUrl must use http:// (got ${parsed.protocol})`);
  }
  const host = parsed.hostname.replace(/^\[|\]$/g, ""); // strip IPv6 brackets
  const allowed = ["127.0.0.1", "localhost", "::1"];
  if (!allowed.includes(host)) {
    throw new Error(
      `PULSE baseUrl must be localhost (got ${parsed.hostname}). ` +
      `Allowed: ${allowed.join(", ")}`
    );
  }
  if (parsed.username || parsed.password) {
    throw new Error("PULSE baseUrl must not contain credentials.");
  }
  return `${parsed.protocol}//${parsed.host}`;
}

export class PulseClient {
  private baseUrl: string;
  private timeout: number;
  private sessionToken: string | null = null;

  constructor(baseUrl: string, timeout = 10_000) {
    this.baseUrl = validateBaseUrl(baseUrl);
    this.timeout = timeout;
  }

  /** Set session token for sidecar auth. */
  setSessionToken(token: string | null): void {
    this.sessionToken = token;
  }

  async healthCheck(): Promise<boolean> {
    try {
      const res = await this.fetch("/healthz");
      return res.status === "ok";
    } catch {
      return false;
    }
  }

  async search(query: string, maxResults = 10): Promise<BrainSearchResult> {
    const q = query.slice(0, 1_000); // Max 1000 chars
    return this.fetch(`/v1/brain/search?q=${encodeURIComponent(q)}`);
  }

  async getBriefing(query: string): Promise<string> {
    const data: BriefingResult = await this.fetch(
      `/v1/brain/briefing?q=${encodeURIComponent(query)}`
    );
    return data.briefing || "";
  }

  async capture(
    userPrompt: string,
    agentResponse: string,
    sessionId = ""
  ): Promise<CaptureResult> {
    return this.fetch("/v1/brain/capture", {
      method: "POST",
      body: JSON.stringify({
        user_prompt: userPrompt.slice(0, 10_000),
        agent_response: agentResponse.slice(0, 50_000),
        session_id: (sessionId || "").slice(0, 128),
      }),
    });
  }

  async save(
    agentName: string,
    learnings: string[] = [],
    failures: string[] = [],
    domain = "general",
    outcome = ""
  ): Promise<SaveResult> {
    return this.fetch("/v1/brain/save", {
      method: "POST",
      body: JSON.stringify({
        agent_name: agentName,
        learnings,
        failures,
        domain,
        outcome,
      }),
    });
  }

  async getLessons(limit = 50): Promise<{ lessons: string[]; count: number }> {
    return this.fetch(`/v1/brain/lessons?limit=${limit}`);
  }

  async getFailures(limit = 50): Promise<{ failures: string[]; count: number }> {
    return this.fetch(`/v1/brain/failures?limit=${limit}`);
  }

  async getPatterns(limit = 50): Promise<{ patterns: string[]; count: number }> {
    return this.fetch(`/v1/brain/patterns?limit=${limit}`);
  }

  async getAntiPatterns(): Promise<{ anti_patterns: unknown[]; count: number }> {
    return this.fetch("/v1/brain/anti_patterns");
  }

  private async fetch(path: string, init?: RequestInit): Promise<any> {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), this.timeout);
    try {
      const headers: Record<string, string> = {
        "Content-Type": "application/json",
        ...(init?.headers as Record<string, string>),
      };
      // Attach session token if available (sidecar auth)
      if (this.sessionToken) {
        headers["X-Pulse-Session"] = this.sessionToken;
      }
      const res = await globalThis.fetch(`${this.baseUrl}${path}`, {
        ...init,
        headers,
        signal: controller.signal,
      });
      if (!res.ok) {
        // Don't leak server error details — generic message only
        throw new Error(`PULSE API error (HTTP ${res.status})`);
      }
      return res.json();
    } finally {
      clearTimeout(timer);
    }
  }
}
