import type { AuditConfig, AuditScenario, AuditReport } from './types';
import { buildPrompt } from './prompt-builder';
import { parseLLMResponse, buildAuditReport } from './parser';

interface OpenAIMessage {
  role: 'system' | 'user' | 'assistant';
  content: string;
}

interface OpenAIRequest {
  model: string;
  messages: OpenAIMessage[];
  temperature: number;
  response_format: { type: 'json_object' };
}

interface AnthropicRequest {
  model: string;
  messages: { role: 'user' | 'assistant'; content: string }[];
  system?: string;
  temperature: number;
  max_tokens: number;
}

export async function runLLMAudit(
  config: AuditConfig,
  systemPrompt: string,
  userPrompt: string,
  onProgress?: (msg: string) => void,
  signal?: AbortSignal,
): Promise<string> {
  config = { ...config, apiKey: config.apiKey?.trim() || '' };
  if (config.provider === 'openai') {
    return callOpenAI(config, systemPrompt, userPrompt, onProgress, signal);
  } else if (config.provider === 'anthropic') {
    return callAnthropic(config, systemPrompt, userPrompt, onProgress, signal);
  } else if (config.provider === 'minimax') {
    return callMinimax(config, systemPrompt, userPrompt, onProgress, signal);
  } else if (config.provider === 'openrouter') {
    return callOpenRouter(config, systemPrompt, userPrompt, onProgress, signal);
  } else {
    return callOllama(config, systemPrompt, userPrompt, onProgress, signal);
  }
}

async function callOpenAI(
  config: AuditConfig,
  systemPrompt: string,
  userPrompt: string,
  onProgress?: (msg: string) => void,
  signal?: AbortSignal,
): Promise<string> {
  onProgress?.('Enviando para OpenAI...');
  const url = 'https://api.openai.com/v1/chat/completions';
  const body: OpenAIRequest = {
    model: config.model,
    messages: [
      { role: 'system', content: systemPrompt },
      { role: 'user', content: userPrompt },
    ],
    temperature: 0,
    response_format: { type: 'json_object' },
  };
  const response = await fetchWithTimeout(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${config.apiKey}`,
    },
    body: JSON.stringify(body),
  }, 600000, signal);
  const data = await response.json() as { choices?: { message?: { content?: string } }[]; error?: { message?: string } };
  if (data.error) throw new Error(`OpenAI error: ${data.error.message}`);
  const content = data.choices?.[0]?.message?.content;
  if (!content) throw new Error('OpenAI returned empty response');
  return content;
}

async function callAnthropic(
  config: AuditConfig,
  systemPrompt: string,
  userPrompt: string,
  onProgress?: (msg: string) => void,
  signal?: AbortSignal,
): Promise<string> {
  onProgress?.('Enviando para Anthropic...');
  const url = 'https://api.anthropic.com/v1/messages';
  const body: AnthropicRequest = {
    model: config.model,
    messages: [
      { role: 'user', content: `${systemPrompt}\n\n${userPrompt}` },
    ],
    system: systemPrompt,
    temperature: 0,
    max_tokens: 8192,
  };
  const response = await fetchWithTimeout(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': config.apiKey,
      'anthropic-version': '2023-06-01',
      'anthropic-dangerous-direct-browser-access': 'true',
    },
    body: JSON.stringify(body),
  }, 600000, signal);
  const data = await response.json() as { content?: { text?: string }[]; error?: { type?: string; message?: string } };
  if (data.error) throw new Error(`Anthropic error: ${data.error.message}`);
  const text = data.content?.[0]?.text;
  if (!text) throw new Error('Anthropic returned empty response');
  return text;
}

async function callMinimax(
  config: AuditConfig,
  systemPrompt: string,
  userPrompt: string,
  onProgress?: (msg: string) => void,
  signal?: AbortSignal,
): Promise<string> {
  onProgress?.('Enviando para MiniMax...');
  let url = config.baseUrl || 'https://api.minimax.io/v1/chat/completions';
  if (config.baseUrl && !config.baseUrl.includes('/chat/completions')) {
    url = config.baseUrl.replace(/\/$/, '') + '/chat/completions';
  }
  const body = {
    model: config.model,
    messages: [
      { role: 'system', content: systemPrompt },
      { role: 'user', content: userPrompt },
    ],
    temperature: 0,
    stream: true,
    response_format: { type: 'json_object' },
  };
  const response = await fetchWithTimeout(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${config.apiKey}`,
    },
    body: JSON.stringify(body),
  }, 600000, signal);
  if (!response.ok) {
    const errText = await response.text();
    throw new Error(`MiniMax HTTP ${response.status}: ${errText.substring(0, 100)}...`);
  }
  
  if (!response.body) throw new Error('MiniMax response body is null');
  
  const reader = response.body.getReader();
  const decoder = new TextDecoder('utf-8');
  let fullText = '';
  let buffer = '';
  
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split('\n');
    buffer = lines.pop() || '';
    for (const rawLine of lines) {
      const line = rawLine.trim();
      if (!line.startsWith('data: ')) continue;
      const jsonStr = line.replace(/^data: /, '').trim();
      if (jsonStr === '[DONE]') continue;
      try {
        const parsed = JSON.parse(jsonStr);
        const content = parsed.choices?.[0]?.delta?.content || '';
        fullText += content;
      } catch (e) {
        // ignore incomplete JSON
      }
    }
  }
  
  if (!fullText) throw new Error('MiniMax returned empty streaming response');
  return fullText;
}

async function callOpenRouter(
  config: AuditConfig,
  systemPrompt: string,
  userPrompt: string,
  onProgress?: (msg: string) => void,
  signal?: AbortSignal,
): Promise<string> {
  onProgress?.('Enviando para OpenRouter (streaming)...');
  let url = config.baseUrl || 'https://openrouter.ai/api/v1/chat/completions';
  if (config.baseUrl && !config.baseUrl.includes('/chat/completions')) {
    url = config.baseUrl.replace(/\/$/, '') + '/chat/completions';
  }
  const body = {
    model: config.model,
    messages: [
      { role: 'system' as const, content: systemPrompt },
      { role: 'user' as const, content: userPrompt },
    ],
    temperature: 0,
    stream: true,
  };
  const response = await fetchWithTimeout(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${config.apiKey}`,
      'HTTP-Referer': 'https://brainwind.app',
      'X-Title': 'BrainWind NBR 6123',
    },
    body: JSON.stringify(body),
  }, 600000, signal);
  
  if (!response.ok) {
    const errText = await response.text();
    throw new Error(`OpenRouter HTTP ${response.status}: ${errText}`);
  }
  
  if (!response.body) throw new Error('OpenRouter response body is null');
  
  const reader = response.body.getReader();
  const decoder = new TextDecoder('utf-8');
  let fullText = '';
  let buffer = '';
  
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split('\n');
    buffer = lines.pop() || '';
    for (const rawLine of lines) {
      const line = rawLine.trim();
      if (!line.startsWith('data: ')) continue;
      const jsonStr = line.replace(/^data: /, '').trim();
      if (jsonStr === '[DONE]') continue;
      try {
        const parsed = JSON.parse(jsonStr);
        // OpenRouter typically sends reasoning tokens or content in delta
        const delta = parsed.choices?.[0]?.delta;
        if (delta?.content) fullText += delta.content;
      } catch (e) {
        // ignore incomplete JSON
      }
    }
  }
  
  if (!fullText) throw new Error('OpenRouter returned empty streaming response');
  return fullText;
}

async function callOllama(
  config: AuditConfig,
  systemPrompt: string,
  userPrompt: string,
  onProgress?: (msg: string) => void,
  signal?: AbortSignal,
): Promise<string> {
  onProgress?.('Enviando para Ollama (local)...');
  const baseUrl = config.baseUrl || 'http://localhost:11434';
  const body = {
    model: config.model,
    messages: [
      { role: 'system', content: systemPrompt },
      { role: 'user', content: userPrompt },
    ],
    stream: false,
    format: 'json',
    options: { temperature: 0 },
  };
  const response = await fetchWithTimeout(`${baseUrl}/api/chat`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  }, 600000, signal);
  const data = await response.json() as { message?: { content?: string }; error?: string };
  if (data.error) throw new Error(`Ollama error: ${data.error}`);
  if (!data.message?.content) throw new Error('Ollama returned empty response');
  return data.message.content;
}

export async function runAudit(
  config: AuditConfig,
  scenarios: AuditScenario[],
  onProgress?: (progress: number) => void,
  signal?: AbortSignal,
): Promise<AuditReport> {
  onProgress?.(0);
  const maxRetriesPerChunk = 2;
  const chunkSize = config.provider === 'minimax' ? 1 : 5; // Reduzido para 1 no MiniMax devido a timeouts (504) por geração excessiva de tokens de raciocínio.
  
  const allLlmResults = [];
  let completed = 0;
  let rawResponseFull = '';
  
  for (let i = 0; i < scenarios.length; i += chunkSize) {
    if (signal?.aborted) {
      throw new Error('Abortado pelo usuário');
    }
    const chunk = scenarios.slice(i, i + chunkSize);
    let chunkSuccess = false;
    let chunkError: Error | null = null;
    
    for (let retry = 0; retry <= maxRetriesPerChunk; retry++) {
      if (signal?.aborted) {
        throw new Error('Abortado pelo usuário');
      }
      try {
        const prompt = buildPrompt(chunk);
        // Avisar a UI do progresso absoluto
        onProgress?.(completed);
        
        const rawResponse = await runLLMAudit(config, prompt.system, prompt.user, () => {
          // opcional
        }, signal);
        rawResponseFull += rawResponse + '\n\n';
        
        const llmResults = parseLLMResponse(rawResponse, chunk.map(s => s.id));
        allLlmResults.push(...llmResults);
        chunkSuccess = true;
        break; // Sucesso, sai do retry loop
      } catch (e) {
        chunkError = e instanceof Error ? e : new Error(String(e));
        console.warn(`Erro no chunk ${i} (tentativa ${retry}):`, chunkError);
        if (retry < maxRetriesPerChunk) {
          // Backoff de 3 segundos antes da próxima tentativa
          await new Promise(r => setTimeout(r, 3000));
        }
      }
    }
    
    if (!chunkSuccess) {
      throw chunkError ?? new Error(`Falha ao auditar cenários no índice ${i} após retentativas.`);
    }
    completed += chunk.length;
    onProgress?.(completed);
  }

  const scenarioMap = new Map(scenarios.map(s => [s.id, { module: s.module, moduleLabel: s.moduleLabel }]));
  const report = buildAuditReport(allLlmResults, scenarioMap, config.provider, config.model, rawResponseFull);
  return report;
}

async function fetchWithTimeout(
  url: string,
  init: RequestInit,
  timeoutMs: number,
  parentSignal?: AbortSignal,
): Promise<Response> {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
  
  if (parentSignal) {
    parentSignal.addEventListener('abort', () => controller.abort());
    if (parentSignal.aborted) controller.abort();
  }

  try {
    const response = await fetch(url, { ...init, signal: controller.signal });
    return response;
  } finally {
    clearTimeout(timeout);
  }
}
