// ============================================================
// AI.JS — AI client
// Primary path: LiteLLM / OpenAI-compatible base URL via LITELLM_API_BASE.
// Legacy direct providers remain available only when explicitly selected.
// ============================================================

const { OpenAI } = require('openai');
const { DEFAULT_MODEL, FALLBACK_MODEL, getBedrockModelId, getBedrockMaxOut } = require('./models');
const logger = require('./logger');

var activeProvider = process.env.AI_PROVIDER || (process.env.LITELLM_API_BASE ? 'litellm' : 'openrouter');

// ============================================================
// OPENROUTER CLIENT (default)
// ============================================================
var openrouter = null;
if (process.env.OPENROUTER_API_KEY) {
  openrouter = new OpenAI({
    baseURL: 'https://openrouter.ai/api/v1',
    apiKey: process.env.OPENROUTER_API_KEY,
    defaultHeaders: {
      'HTTP-Referer': process.env.APP_URL || 'http://localhost:3000',
      'X-Title': 'Pediatric AI Scribe'
    }
  });
  console.log('✅ OpenRouter: configured');
}

// ============================================================
// AWS BEDROCK CLIENT (optional, HIPAA compliant)
// ============================================================
var bedrockClient = null;
if (process.env.AWS_BEDROCK_REGION) {
  try {
    var BedrockModule = require('@aws-sdk/client-bedrock-runtime');
    var BedrockRuntimeClient = BedrockModule.BedrockRuntimeClient;
    bedrockClient = new BedrockRuntimeClient({
      region: process.env.AWS_BEDROCK_REGION || 'us-east-1',
      credentials: process.env.AWS_ACCESS_KEY_ID ? {
        accessKeyId: process.env.AWS_ACCESS_KEY_ID,
        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
      } : undefined
    });
    activeProvider = 'bedrock';
    console.log('✅ AWS Bedrock: configured (region: ' + process.env.AWS_BEDROCK_REGION + ')');
  } catch (e) {
    console.log('⚠️  AWS Bedrock: SDK not installed. Install with: npm install @aws-sdk/client-bedrock-runtime');
    console.log('⚠️  Falling back to OpenRouter');
    activeProvider = 'openrouter';
  }
}

// ============================================================
// AZURE OPENAI CLIENT (optional, HIPAA compliant)
// ============================================================
var azureClient = null;
if (process.env.AZURE_OPENAI_ENDPOINT) {
  try {
    azureClient = new OpenAI({
      apiKey: process.env.AZURE_OPENAI_API_KEY,
      baseURL: process.env.AZURE_OPENAI_ENDPOINT.replace(/\/+$/, '') + '/openai/deployments/' + (process.env.AZURE_DEPLOYMENT_NAME || 'gpt-4o-mini'),
      defaultQuery: { 'api-version': process.env.AZURE_OPENAI_API_VERSION || '2024-08-01-preview' },
      defaultHeaders: { 'api-key': process.env.AZURE_OPENAI_API_KEY }
    });
    activeProvider = 'azure';
    console.log('✅ Azure OpenAI: configured (deployment: ' + (process.env.AZURE_DEPLOYMENT_NAME || 'gpt-4o-mini') + ')');
  } catch (e) {
    console.log('⚠️  Azure OpenAI: configuration failed:', e.message);
    console.log('⚠️  Falling back to OpenRouter');
    activeProvider = 'openrouter';
  }
}

// ============================================================
// GOOGLE VERTEX AI CLIENT (optional, HIPAA compliant with BAA)
// ============================================================
var vertexClient = null;
if (process.env.GOOGLE_VERTEX_PROJECT) {
  try {
    var VertexAI = require('@google-cloud/vertexai');
    vertexClient = new VertexAI.VertexAI({
      project: process.env.GOOGLE_VERTEX_PROJECT,
      location: process.env.GOOGLE_VERTEX_LOCATION || 'us-central1'
    });
    activeProvider = 'vertex';
    console.log('✅ Google Vertex AI: configured (project: ' + process.env.GOOGLE_VERTEX_PROJECT + ', location: ' + (process.env.GOOGLE_VERTEX_LOCATION || 'us-central1') + ')');
  } catch (e) {
    console.log('⚠️  Google Vertex AI: SDK not installed. Install with: npm install @google-cloud/vertexai');
    console.log('⚠️  Falling back to OpenRouter');
    activeProvider = 'openrouter';
  }
}

// ============================================================
// LITELLM CLIENT (optional — OpenAI-compatible proxy)
// ============================================================
var litellmClient = null;
if (process.env.LITELLM_API_BASE) {
  try {
    litellmClient = new OpenAI({
      baseURL: process.env.LITELLM_API_BASE.replace(/\/+$/, ''),
      apiKey: process.env.LITELLM_API_KEY || process.env.LITELLM_MASTER_KEY || process.env.OPENAI_API_KEY || 'sk-litellm'
    });
    activeProvider = 'litellm';
    console.log('✅ LiteLLM: configured (base: ' + process.env.LITELLM_API_BASE + ')');
  } catch (e) {
    console.log('⚠️  LiteLLM: configuration failed:', e.message);
    console.log('⚠️  Falling back to OpenRouter');
    activeProvider = 'openrouter';
  }
}

// Force provider from env if explicitly set
if (process.env.AI_PROVIDER) {
  activeProvider = process.env.AI_PROVIDER;
}

// Validate: if chosen provider has no client, fall back
if (activeProvider === 'bedrock' && !bedrockClient) {
  console.log('⚠️  Bedrock selected but not available. Falling back to OpenRouter.');
  activeProvider = 'openrouter';
}
if (activeProvider === 'azure' && !azureClient) {
  console.log('⚠️  Azure selected but not available. Falling back to OpenRouter.');
  activeProvider = 'openrouter';
}
if (activeProvider === 'vertex' && !vertexClient) {
  console.log('⚠️  Vertex AI selected but not available. Falling back to OpenRouter.');
  activeProvider = 'openrouter';
}
if (activeProvider === 'litellm' && !litellmClient) {
  console.log('⚠️  LiteLLM selected but not available. Falling back to OpenRouter.');
  activeProvider = 'openrouter';
}
if (activeProvider === 'openrouter' && !openrouter) {
  console.error('❌ OpenRouter selected but OPENROUTER_API_KEY not set!');
}

console.log('🤖 Active AI provider:', activeProvider);

// ============================================================
// CALL OPENROUTER
// ============================================================
async function callOpenRouter(messages, model, temperature, maxTokens) {
  if (!openrouter) throw new Error('OpenRouter not configured. Set OPENROUTER_API_KEY in .env');

  var completion = await openrouter.chat.completions.create({
    model: model,
    messages: messages,
    temperature: temperature,
    max_tokens: maxTokens
  });

  return {
    success: true,
    content: completion.choices[0].message.content,
    model: model,
    provider: 'openrouter',
    usage: completion.usage || null,
    finishReason: completion.choices[0].finish_reason || null
  };
}

// ============================================================
// CALL AZURE OPENAI
// ============================================================
async function callAzure(messages, model, temperature, maxTokens) {
  if (!azureClient) throw new Error('Azure OpenAI not configured');

  var completion = await azureClient.chat.completions.create({
    model: process.env.AZURE_DEPLOYMENT_NAME || 'gpt-4o-mini',
    messages: messages,
    temperature: temperature,
    max_tokens: maxTokens
  });

  return {
    success: true,
    content: completion.choices[0].message.content,
    model: process.env.AZURE_DEPLOYMENT_NAME || model,
    provider: 'azure',
    usage: completion.usage || null,
    finishReason: completion.choices[0].finish_reason || null
  };
}

// ============================================================
// CALL AWS BEDROCK
// Anthropic models use InvokeModel (Messages API)
// All other models use Converse API (unified cross-model API)
// ============================================================
async function callBedrock(messages, model, temperature, maxTokens) {
  if (!bedrockClient) throw new Error('AWS Bedrock not configured');

  var BedrockModule = require('@aws-sdk/client-bedrock-runtime');
  var modelId = getBedrockModelId(model);
  var isAnthropic = modelId.indexOf('anthropic.') !== -1;

  // Clamp maxTokens to model limit if set
  var modelMax = getBedrockMaxOut(model);
  if (modelMax && maxTokens > modelMax) maxTokens = modelMax;

  // Separate system message from chat messages
  var systemMsg = '';
  var chatMessages = [];
  messages.forEach(function(m) {
    if (m.role === 'system') {
      systemMsg += (systemMsg ? '\n' : '') + m.content;
    } else {
      chatMessages.push({ role: m.role, content: m.content });
    }
  });

  if (isAnthropic) {
    // Anthropic Messages API format (native, best performance)
    var InvokeModelCommand = BedrockModule.InvokeModelCommand;
    var body = {
      anthropic_version: 'bedrock-2023-05-31',
      max_tokens: maxTokens,
      temperature: temperature,
      messages: chatMessages
    };
    if (systemMsg) body.system = systemMsg;

    var command = new InvokeModelCommand({
      modelId: modelId,
      contentType: 'application/json',
      accept: 'application/json',
      body: JSON.stringify(body)
    });

    var response = await bedrockClient.send(command);
    var responseBody = JSON.parse(new TextDecoder().decode(response.body));

    // Debug: log response structure for troubleshooting
    if (Array.isArray(responseBody.content)) {
      console.log('[Bedrock] Model:', modelId, '| Blocks:', responseBody.content.length, '|', responseBody.content.map(function(b) { return b.type + '(' + (b.text ? b.text.length : 0) + ')'; }).join(', '), '| stop_reason:', responseBody.stop_reason);
    }

    // Opus 4.6+ may return multiple content blocks (thinking + text).
    // Extract all text blocks, skipping thinking blocks.
    var textContent = '';
    if (Array.isArray(responseBody.content)) {
      responseBody.content.forEach(function(block) {
        if (block.type === 'text' && block.text) textContent += block.text;
      });
      // Fallback: if no text blocks found, try first block
      if (!textContent && responseBody.content[0]) {
        textContent = responseBody.content[0].text || '';
      }
    } else {
      textContent = responseBody.content || '';
    }

    return {
      success: true,
      content: textContent,
      model: modelId,
      provider: 'bedrock',
      usage: {
        prompt_tokens: responseBody.usage ? responseBody.usage.input_tokens : 0,
        completion_tokens: responseBody.usage ? responseBody.usage.output_tokens : 0
      }
    };
  } else {
    // Converse API — unified API for all non-Anthropic Bedrock models
    var ConverseCommand = BedrockModule.ConverseCommand;

    var converseMessages = chatMessages.map(function(m) {
      return { role: m.role, content: [{ text: m.content }] };
    });

    var converseParams = {
      modelId: modelId,
      messages: converseMessages,
      inferenceConfig: {
        maxTokens: maxTokens,
        temperature: temperature
      }
    };
    if (systemMsg) {
      converseParams.system = [{ text: systemMsg }];
    }

    var converseResponse = await bedrockClient.send(new ConverseCommand(converseParams));

    var outputText = '';
    if (converseResponse.output && converseResponse.output.message && converseResponse.output.message.content) {
      converseResponse.output.message.content.forEach(function(block) {
        if (block.text) outputText += block.text;
      });
    }

    return {
      success: true,
      content: outputText,
      model: modelId,
      provider: 'bedrock',
      usage: {
        prompt_tokens: converseResponse.usage ? converseResponse.usage.inputTokens : 0,
        completion_tokens: converseResponse.usage ? converseResponse.usage.outputTokens : 0
      }
    };
  }
}

// ============================================================
// CALL GOOGLE VERTEX AI
// Uses the Gemini generateContent API
// ============================================================
async function callVertex(messages, model, temperature, maxTokens) {
  if (!vertexClient) throw new Error('Google Vertex AI not configured');

  // Map model ID to Vertex model name
  var { getVertexModelId } = require('./models');
  var vertexModelId = getVertexModelId(model);

  var generativeModel = vertexClient.getGenerativeModel({
    model: vertexModelId,
    generationConfig: {
      temperature: temperature,
      maxOutputTokens: maxTokens,
    },
  });

  // Convert OpenAI-style messages to Vertex AI format
  var systemInstruction = '';
  var contents = [];
  messages.forEach(function(m) {
    if (m.role === 'system') {
      systemInstruction += (systemInstruction ? '\n' : '') + m.content;
    } else {
      contents.push({
        role: m.role === 'assistant' ? 'model' : 'user',
        parts: [{ text: m.content }]
      });
    }
  });

  var request = { contents: contents };
  if (systemInstruction) {
    request.systemInstruction = { parts: [{ text: systemInstruction }] };
  }

  var result = await generativeModel.generateContent(request);
  var response = result.response;

  var textContent = '';
  if (response.candidates && response.candidates[0] && response.candidates[0].content) {
    response.candidates[0].content.parts.forEach(function(part) {
      if (part.text) textContent += part.text;
    });
  }

  return {
    success: true,
    content: textContent,
    model: vertexModelId,
    provider: 'vertex',
    usage: response.usageMetadata ? {
      prompt_tokens: response.usageMetadata.promptTokenCount || 0,
      completion_tokens: response.usageMetadata.candidatesTokenCount || 0
    } : null
  };
}

// ============================================================
// CALL LITELLM (OpenAI-compatible proxy)
// ============================================================
async function callLiteLLM(messages, model, temperature, maxTokens) {
  if (!litellmClient) throw new Error('LiteLLM not configured. Set LITELLM_API_BASE in .env');

  var completion = await litellmClient.chat.completions.create({
    model: model,
    messages: messages,
    temperature: temperature,
    max_tokens: maxTokens
  });

  return {
    success: true,
    content: completion.choices[0].message.content,
    model: model,
    provider: 'litellm',
    usage: completion.usage || null,
    finishReason: completion.choices[0].finish_reason || null
  };
}

async function assertModelAllowed(requestedModel, options) {
  options = options || {};
  if (!requestedModel || options.skipAllowlistCheck) return;
  try {
    var db = require('../db/database');
    var { getAllowedModelIds } = require('./models');
    var allowed = await getAllowedModelIds(db);
    if (allowed && allowed.size > 0 && !allowed.has(requestedModel)) {
      var err = new Error('Model not permitted');
      err.code = 'model_not_permitted';
      err.model = requestedModel;
      throw err;
    }
  } catch (e) {
    if (e && e.code === 'model_not_permitted') throw e;
    console.warn('[callAI] Allow-list check skipped:', e && e.message);
  }
}

async function resolveModel(requestedModel) {
  var model = requestedModel && String(requestedModel).trim();
  if (model) return model;
  try {
    var db = require('../db/database');
    var adminDefault = await db.getSetting('models.default');
    if (adminDefault && String(adminDefault).trim()) return String(adminDefault).trim();
  } catch (e) {}
  return DEFAULT_MODEL;
}

async function callAIStream(messages, options, onToken) {
  options = options || {};
  var requestedModel = options.model;
  var model = await resolveModel(requestedModel);
  var temperature = options.temperature || 0.3;
  var maxTokens = options.maxTokens || 4000;
  var startTime = Date.now();
  await assertModelAllowed(model, options);

  var client = null;
  var provider = null;
  if (activeProvider === 'litellm' && litellmClient) {
    client = litellmClient;
    provider = 'litellm';
  } else if (activeProvider === 'openrouter' && openrouter) {
    client = openrouter;
    provider = 'openrouter';
  } else if (activeProvider === 'azure' && azureClient) {
    client = azureClient;
    provider = 'azure';
    model = process.env.AZURE_DEPLOYMENT_NAME || model;
  }
  if (!client) throw new Error('Streaming is only configured for OpenAI-compatible providers');

  var content = '';
  var finishReason = null;
  var stream = await client.chat.completions.create({
    model: model,
    messages: messages,
    temperature: temperature,
    max_tokens: maxTokens,
    stream: true
  });
  for await (var part of stream) {
    var choice = part && part.choices && part.choices[0] ? part.choices[0] : null;
    if (choice && choice.finish_reason) finishReason = choice.finish_reason;
    var delta = choice && choice.delta ? choice.delta.content : '';
    if (!delta) continue;
    content += delta;
    if (typeof onToken === 'function') onToken(delta);
  }
  var duration = Date.now() - startTime;
  logger.apiCall(null, provider + '/' + model, { model: model, duration: duration, statusCode: 200 });
  return { success: true, content: content, model: model, provider: provider, duration: duration, finishReason: finishReason };
}

// ============================================================
// MAIN CALL AI FUNCTION — Routes to correct provider
// ============================================================
async function callAI(messages, options) {
  options = options || {};
  var requestedModel = options.model;
  var model = await resolveModel(requestedModel);
  var temperature = options.temperature || 0.3;
  var maxTokens = options.maxTokens || 4000;
  var startTime = Date.now();

  // Server-side whitelist: reject any model the operator hasn't enabled.
  // Prevents a client from passing e.g. model:"openai/o1" and draining
  // the budget on a reasoning model outside the configured roster.
  // Falls back to the admin default, then DEFAULT_MODEL, when no model was provided.
  // Admin test endpoints pass skipAllowlistCheck to test before adding.
  await assertModelAllowed(model, options);

  try {
    var result;

    // Route to correct provider
    if (activeProvider === 'bedrock' && bedrockClient) {
      result = await callBedrock(messages, model, temperature, maxTokens);
    } else if (activeProvider === 'azure' && azureClient) {
      result = await callAzure(messages, model, temperature, maxTokens);
    } else if (activeProvider === 'vertex' && vertexClient) {
      result = await callVertex(messages, model, temperature, maxTokens);
    } else if (activeProvider === 'litellm' && litellmClient) {
      result = await callLiteLLM(messages, model, temperature, maxTokens);
    } else if (openrouter) {
      result = await callOpenRouter(messages, model, temperature, maxTokens);
    } else {
      throw new Error('No AI provider configured. Set LITELLM_API_BASE plus an API key, or explicitly configure a legacy direct provider.');
    }

    var duration = Date.now() - startTime;

    var usage = result.usage || {};
    logger.apiCall(null, activeProvider + '/' + (result.model || model), {
      model: result.model || model,
      tokensInput: usage.prompt_tokens || 0,
      tokensOutput: usage.completion_tokens || 0,
      duration: duration,
      statusCode: 200
    });

    result.duration = duration;
    return result;

  } catch (err) {
    duration = Date.now() - startTime;
    logger.error('AI call failed', {
      provider: activeProvider,
      model: model,
      error: err.message,
      duration: duration
    });

    // Try fallback model (on OpenRouter or LiteLLM) — OPT-IN ONLY.
    // Silent fallback is a HIPAA landmine: if the primary provider is
    // BAA-covered and the fallback isn't, PHI can leak to a non-covered
    // endpoint without any admin awareness. Requires admin setting
    // `ai.allow_model_fallback = true` (default false).
    var allowFallback = false;
    try {
      var _db = require('../db/database');
      var setting = await _db.getSetting('ai.allow_model_fallback');
      allowFallback = (setting === 'true');
    } catch (_e) { allowFallback = false; }

    if (allowFallback) {
      if (activeProvider === 'openrouter' && model !== FALLBACK_MODEL && openrouter) {
        logger.warn('Trying fallback model: ' + FALLBACK_MODEL);
        try {
          var fallbackResult = await callOpenRouter(messages, FALLBACK_MODEL, temperature, maxTokens);
          fallbackResult.fallback = true;
          fallbackResult.duration = Date.now() - startTime;
          logger.info('Fallback success', { model: FALLBACK_MODEL });
          return fallbackResult;
        } catch (err2) {
          logger.error('Fallback also failed', { error: err2.message });
          throw new Error('All models failed: ' + err2.message);
        }
      }

      if (activeProvider === 'litellm' && model !== FALLBACK_MODEL && litellmClient) {
        logger.warn('Trying fallback model on LiteLLM: ' + FALLBACK_MODEL);
        try {
          var litellmFallback = await callLiteLLM(messages, FALLBACK_MODEL, temperature, maxTokens);
          litellmFallback.fallback = true;
          litellmFallback.duration = Date.now() - startTime;
          logger.info('LiteLLM fallback success', { model: FALLBACK_MODEL });
          return litellmFallback;
        } catch (err3) {
          logger.error('LiteLLM fallback also failed', { error: err3.message });
          throw new Error('All models failed: ' + err3.message);
        }
      }
    }

    throw err;
  }
}

// ============================================================
// DISCOVER MODELS — Query provider APIs for available models
// Used by admin panel to search & select models dynamically
// ============================================================
async function discoverModels() {
  var discovered = [];

  if (activeProvider === 'litellm' && litellmClient) {
    try {
      var models = await litellmClient.models.list();
      if (models && models.data) {
        models.data.forEach(function(m) {
          discovered.push({
            id: m.id,
            name: m.id,
            cost: '?',
            category: 'smart',
            tag: 'LITELLM',
            source: 'litellm-api'
          });
        });
      }
    } catch (e) {
      logger.warn('LiteLLM model discovery failed: ' + e.message);
    }
  }

  if (activeProvider === 'vertex' && vertexClient) {
    // Vertex AI doesn't have a simple list API, return known Gemini models
    var { VERTEX_MODELS } = require('./models');
    VERTEX_MODELS.forEach(function(m) {
      discovered.push(Object.assign({}, m, { source: 'vertex-builtin' }));
    });
  }

  if (activeProvider === 'openrouter' && openrouter) {
    try {
      var axios = require('axios');
      var resp = await axios.get('https://openrouter.ai/api/v1/models', {
        headers: { 'Authorization': 'Bearer ' + process.env.OPENROUTER_API_KEY }
      });
      if (resp.data && resp.data.data) {
        resp.data.data.forEach(function(m) {
          var pricing = m.pricing || {};
          var promptCost = parseFloat(pricing.prompt || 0);
          var costStr = promptCost > 0 ? '~$' + (promptCost * 1000000).toFixed(3) + '/M' : 'FREE';
          discovered.push({
            id: m.id,
            name: m.name || m.id,
            cost: costStr,
            category: promptCost === 0 ? 'free' : (promptCost < 0.000003 ? 'fast' : (promptCost < 0.00001 ? 'smart' : 'premium')),
            tag: promptCost === 0 ? 'FREE' : 'API',
            source: 'openrouter-api'
          });
        });
      }
    } catch (e) {
      logger.warn('OpenRouter model discovery failed: ' + e.message);
    }
  }

  // Bedrock: try live ListFoundationModels, fall back to built-in list
  if (activeProvider === 'bedrock') {
    var bedrockDiscovered = false;
    try {
      var BedrockListModule = require('@aws-sdk/client-bedrock');
      var listClient = new BedrockListModule.BedrockClient({
        region: process.env.AWS_BEDROCK_REGION || 'us-east-1',
        credentials: process.env.AWS_ACCESS_KEY_ID ? {
          accessKeyId: process.env.AWS_ACCESS_KEY_ID,
          secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
        } : undefined
      });
      var listResp = await listClient.send(new BedrockListModule.ListFoundationModelsCommand({}));
      if (listResp.modelSummaries && listResp.modelSummaries.length > 0) {
        listResp.modelSummaries.forEach(function(m) {
          if (m.inferenceTypesSupported && m.inferenceTypesSupported.includes('ON_DEMAND')) {
            discovered.push({
              id: m.modelId,
              name: m.modelName || m.modelId,
              cost: '?',
              category: 'smart',
              tag: (m.providerName || 'BEDROCK').toUpperCase(),
              source: 'bedrock-api'
            });
          }
        });
        bedrockDiscovered = true;
      }
    } catch (e) {
      logger.warn('Bedrock live discovery failed (' + e.code + ': ' + e.message + '), using built-in list');
    }
    if (!bedrockDiscovered) {
      // Fall back to built-in BEDROCK_MODELS filtered by region
      var { BEDROCK_MODELS } = require('./models');
      var region = process.env.AWS_BEDROCK_REGION || 'us-east-1';
      BEDROCK_MODELS.filter(function(m) {
        return !m.regions || m.regions.indexOf(region) !== -1;
      }).forEach(function(m) {
        discovered.push(Object.assign({}, m, { source: 'bedrock-builtin' }));
      });
    }
  }

  // Azure: return built-in model list
  if (activeProvider === 'azure') {
    var { AZURE_MODELS } = require('./models');
    AZURE_MODELS.forEach(function(m) {
      discovered.push(Object.assign({}, m, { source: 'azure-builtin' }));
    });
  }

  return discovered;
}

module.exports = { callAI, callAIStream, activeProvider, discoverModels, vertexClient, litellmClient };
