import { getLlamacppUrl, getLlamacppApiKey, getLlamacppPreamble } from '../config.js';
import { mcpClient, McpToolDef } from './mcp.service.js';

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

interface LlamaToolCallResult {
  content: Array<{
    type: string;
    text?: string;
  }>;
  isError?: boolean;
}

interface LlamaToolDefinition {
  type: 'function';
  function: {
    name: string;
    description: string;
    parameters: object;
  };
}

interface LlamaRequest {
  messages: LlamaMessage[];
  tools?: LlamaToolDefinition[];
  tool_choice?: 'auto' | 'none';
}

interface LlamaResponseChoice {
  message?: {
    content?: string;
    tool_calls?: Array<{
      id: string;
      type: string;
      function: { name: string; arguments: string };
    }>;
  };
}

interface LlamaResponse {
  choices?: LlamaResponseChoice[];
}

const MAX_TOOL_ITERATIONS = 10;

export const llamacppService = {
  async chat(messages: Array<{ role: string; content: string }>): Promise<string> {
    let history: LlamaMessage[] = messages.map(m => ({ role: m.role, content: m.content }));

    const apiUrl = getLlamacppUrl();
    if (!apiUrl) return '';

    const apiKey = getLlamacppApiKey();
    const headers: Record<string, string> = { 'Content-Type': 'application/json' };
    if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`;

    const request: LlamaRequest = { messages: history };
    const res = await fetch(apiUrl, { method: 'POST', headers, body: JSON.stringify(request) });

    if (!res.ok) {
      const text = await res.text().catch(() => '');
      throw new Error(`llama.cpp request failed (${res.status}): ${text.slice(0, 300)}`);
    }

    const data = (await res.json()) as LlamaResponse;
    const choice = data.choices?.[0];

    if (!choice?.message || !choice.message.content) {
      return '';
    }

    return choice.message.content.trim();
  },

  async chatWithPreamble(userText: string): Promise<string> {
    const preamble = getLlamacppPreamble();
    const msgs = preamble
      ? [{ role: 'system' as const, content: preamble }, { role: 'user' as const, content: userText }]
      : [{ role: 'user' as const, content: userText }];
    return this.chat(msgs);
  },

  async chatWithMcpTools(userText: string): Promise<string> {
    const preamble = getLlamacppPreamble();
    const initialMessages: LlamaMessage[] = preamble
      ? [{ role: 'system', content: preamble }, { role: 'user', content: userText }]
      : [{ role: 'user', content: userText }];

    if (!mcpClient.getReady()) {
      console.log('[llama] MCP not ready, falling back to preamble-only chat');
      return this.chatWithPreamble(userText);
    }

    const tools = mcpClient.getTools();
    const llamaTools: LlamaToolDefinition[] = tools.map((t) => ({
      type: 'function',
      function: {
        name: t.name,
        description: t.description,
        parameters: t.inputSchema,
      },
    }));

    return this._chatWithTools(initialMessages, llamaTools);
  },

  async _chatWithTools(messages: LlamaMessage[], tools: LlamaToolDefinition[]): Promise<string> {
    const apiUrl = getLlamacppUrl();
    if (!apiUrl) return '';

    let iter = 0;
    const history: LlamaMessage[] = messages.map((m) => ({ ...m }));

    while (iter < MAX_TOOL_ITERATIONS) {
      const apiKey = getLlamacppApiKey();
      const headers: Record<string, string> = { 'Content-Type': 'application/json' };
      if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`;

      const body: LlamaRequest = { messages: history, tools, tool_choice: 'auto' };
      const res = await fetch(apiUrl, { method: 'POST', headers, body: JSON.stringify(body) });

      if (!res.ok) {
        const text = await res.text().catch(() => '');
        throw new Error(`llama.cpp request failed (${res.status}): ${text.slice(0, 300)}`);
      }

      const data = (await res.json()) as LlamaResponse;
      const choice = data.choices?.[0];

      if (!choice?.message) {
        throw new Error('llama.cpp response has no message');
      }

      if (!choice.message.tool_calls || choice.message.tool_calls.length === 0) {
        return choice.message.content?.trim() ?? '';
      }

      history.push({ role: 'assistant', ...choice.message });

      for (const toolCall of choice.message.tool_calls) {
        let resultText = '';
        try {
          const args = JSON.parse(toolCall.function.arguments);
          resultText = await mcpClient.callTool(toolCall.function.name, args);
        } catch (err) {
          const msg = err instanceof Error ? err.message : String(err);
          resultText = `Error calling tool "${toolCall.function.name}": ${msg}`;
        }

        history.push({
          role: 'tool',
          content: resultText,
          tool_call_id: toolCall.id,
        });
      }

      iter++;
    }

    throw new Error(`Exceeded max tool-call iterations (${MAX_TOOL_ITERATIONS})`);
  },
};
