import type { McpdClient } from '../http/mcpd-client.js';

export interface LinkResolution {
  content: string | null;
  status: 'alive' | 'dead' | 'unknown';
  error?: string;
}

interface CacheEntry {
  resolution: LinkResolution;
  expiresAt: number;
}

interface ParsedLink {
  project: string;
  server: string;
  uri: string;
}

/**
 * Resolves prompt links by fetching MCP resources from source projects via mcpd.
 * Link format: project/server:resource-uri
 */
export class LinkResolver {
  private cache = new Map<string, CacheEntry>();

  constructor(
    private readonly mcpdClient: McpdClient,
    private readonly cacheTtlMs = 5 * 60 * 1000, // 5 minutes
  ) {}

  /**
   * Parse a link target string into its components.
   * Format: project/server:resource-uri
   */
  parseLink(linkTarget: string): ParsedLink {
    const slashIdx = linkTarget.indexOf('/');
    if (slashIdx < 1) throw new Error(`Invalid link format (missing project): ${linkTarget}`);

    const project = linkTarget.slice(0, slashIdx);
    const rest = linkTarget.slice(slashIdx + 1);

    const colonIdx = rest.indexOf(':');
    if (colonIdx < 1) throw new Error(`Invalid link format (missing server:uri): ${linkTarget}`);

    const server = rest.slice(0, colonIdx);
    const uri = rest.slice(colonIdx + 1);
    if (!uri) throw new Error(`Invalid link format (empty uri): ${linkTarget}`);

    return { project, server, uri };
  }

  /**
   * Resolve a link target and return the fetched content + status.
   * Results are cached with a configurable TTL.
   */
  async resolve(linkTarget: string): Promise<LinkResolution> {
    // Check cache first
    const cached = this.cache.get(linkTarget);
    if (cached && cached.expiresAt > Date.now()) {
      return cached.resolution;
    }

    let resolution: LinkResolution;
    try {
      const { project, server, uri } = this.parseLink(linkTarget);
      const content = await this.fetchResource(project, server, uri);
      resolution = { content, status: 'alive' };
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      console.error(`[link-resolver] Dead link: ${linkTarget} — ${message}`);
      resolution = { content: null, status: 'dead', error: message };
    }

    // Cache the result
    this.cache.set(linkTarget, {
      resolution,
      expiresAt: Date.now() + this.cacheTtlMs,
    });

    return resolution;
  }

  /**
   * Check link health without returning full content (uses cache if available).
   */
  async checkHealth(linkTarget: string): Promise<'alive' | 'dead' | 'unknown'> {
    const cached = this.cache.get(linkTarget);
    if (cached && cached.expiresAt > Date.now()) {
      return cached.resolution.status;
    }
    // Don't do a full resolve just for health — return unknown
    return 'unknown';
  }

  /** Clear all cached resolutions. */
  clearCache(): void {
    this.cache.clear();
  }

  private async fetchResource(project: string, server: string, uri: string): Promise<string> {
    // Step 1: Resolve server name → server ID from the project's servers
    const servers = await this.mcpdClient.get<Array<{ id: string; name: string }>>(
      `/api/v1/projects/${encodeURIComponent(project)}/servers`,
    );
    const target = servers.find((s) => s.name === server);
    if (!target) {
      throw new Error(`Server '${server}' not found in project '${project}'`);
    }

    // Step 2: Call resources/read via the MCP proxy
    const proxyResponse = await this.mcpdClient.post<{
      result?: { contents?: Array<{ text?: string; uri?: string }> };
      error?: { code: number; message: string };
    }>('/api/v1/mcp/proxy', {
      serverId: target.id,
      method: 'resources/read',
      params: { uri },
    });

    if (proxyResponse.error) {
      throw new Error(`MCP error: ${proxyResponse.error.message}`);
    }

    const contents = proxyResponse.result?.contents;
    if (!contents || contents.length === 0) {
      throw new Error(`No content returned for resource: ${uri}`);
    }

    // Concatenate all text contents
    return contents.map((c) => c.text ?? '').join('\n');
  }
}
