import https from 'node:https';
import type { LlmProvider, CompletionOptions, CompletionResult, ChatMessage, ToolCall } from './types.js';

export interface DeepSeekConfig {
  apiKey: string;
  baseUrl?: string;
  defaultModel?: string;
}

interface DeepSeekMessage {
  role: string;
  content: string | null;
  tool_calls?: Array<{
    id: string;
    type: 'function';
    function: { name: string; arguments: string };
  }>;
  tool_call_id?: string;
  name?: string;
}

/**
 * DeepSeek provider using the OpenAI-compatible chat completions API.
 * Endpoint: https://api.deepseek.com/v1/chat/completions
 */
export class DeepSeekProvider implements LlmProvider {
  readonly name = 'deepseek';
  private apiKey: string;
  private baseUrl: string;
  private defaultModel: string;

  constructor(config: DeepSeekConfig) {
    this.apiKey = config.apiKey;
    this.baseUrl = (config.baseUrl ?? 'https://api.deepseek.com').replace(/\/$/, '');
    this.defaultModel = config.defaultModel ?? 'deepseek-chat';
  }

  async complete(options: CompletionOptions): Promise<CompletionResult> {
    const model = options.model ?? this.defaultModel;
    const body: Record<string, unknown> = {
      model,
      messages: options.messages.map(toDeepSeekMessage),
    };
    if (options.temperature !== undefined) body.temperature = options.temperature;
    if (options.maxTokens !== undefined) body.max_tokens = options.maxTokens;

    if (options.tools && options.tools.length > 0) {
      body.tools = options.tools.map((t) => ({
        type: 'function',
        function: {
          name: t.name,
          description: t.description,
          parameters: t.inputSchema,
        },
      }));
    }

    const response = await this.request('/v1/chat/completions', body);
    return parseResponse(response);
  }

  async listModels(): Promise<string[]> {
    // DeepSeek doesn't have a public models listing endpoint;
    // return well-known models.
    return [
      'deepseek-chat',
      'deepseek-reasoner',
    ];
  }

  async isAvailable(): Promise<boolean> {
    if (!this.apiKey) return false;
    try {
      // Send a minimal request to verify the API key
      await this.complete({
        messages: [{ role: 'user', content: 'hi' }],
        maxTokens: 1,
      });
      return true;
    } catch {
      return false;
    }
  }

  private request(path: string, body: unknown, method = 'POST'): Promise<unknown> {
    return new Promise((resolve, reject) => {
      const url = new URL(path, this.baseUrl);
      const payload = body !== undefined ? JSON.stringify(body) : undefined;
      const opts = {
        hostname: url.hostname,
        port: url.port || 443,
        path: url.pathname,
        method,
        timeout: 120000,
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json',
          ...(payload ? { 'Content-Length': Buffer.byteLength(payload) } : {}),
        },
      };

      const req = https.request(opts, (res) => {
        const chunks: Buffer[] = [];
        res.on('data', (chunk: Buffer) => chunks.push(chunk));
        res.on('end', () => {
          const raw = Buffer.concat(chunks).toString('utf-8');

          // Handle rate limiting
          if (res.statusCode === 429) {
            const retryAfter = res.headers['retry-after'];
            reject(new Error(`DeepSeek rate limit exceeded${retryAfter ? `. Retry after ${retryAfter}s` : ''}`));
            return;
          }

          try {
            resolve(JSON.parse(raw));
          } catch {
            reject(new Error(`Invalid JSON response from DeepSeek ${path}: ${raw.slice(0, 200)}`));
          }
        });
      });
      req.on('error', reject);
      req.on('timeout', () => {
        req.destroy();
        reject(new Error('DeepSeek request timed out'));
      });
      if (payload) req.write(payload);
      req.end();
    });
  }
}

function toDeepSeekMessage(msg: ChatMessage): DeepSeekMessage {
  const result: DeepSeekMessage = {
    role: msg.role,
    content: msg.content,
  };
  if (msg.toolCallId !== undefined) result.tool_call_id = msg.toolCallId;
  if (msg.name !== undefined) result.name = msg.name;
  return result;
}

function parseResponse(raw: unknown): CompletionResult {
  const data = raw as {
    choices?: Array<{
      message?: {
        content?: string | null;
        tool_calls?: Array<{
          id: string;
          function: { name: string; arguments: string };
        }>;
      };
      finish_reason?: string;
    }>;
    usage?: {
      prompt_tokens?: number;
      completion_tokens?: number;
      total_tokens?: number;
    };
  };

  const choice = data.choices?.[0];
  const toolCalls: ToolCall[] = (choice?.message?.tool_calls ?? []).map((tc) => ({
    id: tc.id,
    name: tc.function.name,
    arguments: safeParse(tc.function.arguments),
  }));

  const finishReason = choice?.finish_reason === 'tool_calls' ? 'tool_calls' as const
    : choice?.finish_reason === 'length' ? 'length' as const
    : 'stop' as const;

  return {
    content: choice?.message?.content ?? '',
    toolCalls,
    usage: {
      promptTokens: data.usage?.prompt_tokens ?? 0,
      completionTokens: data.usage?.completion_tokens ?? 0,
      totalTokens: data.usage?.total_tokens ?? 0,
    },
    finishReason,
  };
}

function safeParse(json: string): Record<string, unknown> {
  try {
    return JSON.parse(json) as Record<string, unknown>;
  } catch {
    return {};
  }
}
