'use strict';

const fs = require('fs');
const os = require('os');
const path = require('path');
const http = require('http');
const https = require('https');
const log = require('./logger');

const DEFAULT_BASE_URL = 'https://api.cutos.ai/v1';
const FETCH_TIMEOUT_MS = 10_000;
let busy = false;
let operationId = 0;

function resolvePiModelsConfigFile(env = process.env) {
  const configured = String(env.PI_MODELS_CONFIG || '').trim();
  return configured ? path.resolve(configured) : path.join(os.homedir(), '.pi', 'models.json');
}

function normalizeBaseUrl(baseUrl) {
  let value = String(baseUrl || DEFAULT_BASE_URL).trim().replace(/\/+$/, '');
  if (!/\/v1$/i.test(value)) value = `${value}/v1`;
  return value;
}

function fetchModels(baseUrl, apiKey, callback) {
  const url = `${normalizeBaseUrl(baseUrl)}/models`;
  let parsed;
  try {
    parsed = new URL(url);
  } catch (_) {
    callback(new Error(`invalid base-url: ${url}`));
    return;
  }
  const transport = parsed.protocol === 'https:' ? https : http;
  const req = transport.request({
    hostname: parsed.hostname,
    port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
    path: `${parsed.pathname}${parsed.search}`,
    method: 'GET',
    headers: { Authorization: `Bearer ${apiKey || ''}`, 'Content-Type': 'application/json' },
  }, (res) => {
    let body = '';
    res.on('data', (chunk) => { body += chunk; });
    res.on('end', () => {
      try {
        const payload = JSON.parse(body);
        if (!Array.isArray(payload.data)) {
          callback(new Error(payload.error?.message || `bad models response: ${body.slice(0, 200)}`));
          return;
        }
        callback(null, payload.data
          .filter((model) => model && typeof model.id === 'string' && model.id)
          .map((model) => ({ id: model.id, reasoning: true })));
      } catch (error) {
        callback(new Error(`parse models: ${error.message}`));
      }
    });
  });
  req.on('error', callback);
  req.setTimeout(FETCH_TIMEOUT_MS, () => req.destroy(new Error('models request timeout')));
  req.end();
}

function readConfig(configFile) {
  try {
    const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
    if (!config || typeof config !== 'object' || Array.isArray(config)) throw new Error('root must be a JSON object');
    return config;
  } catch (error) {
    if (error.code === 'ENOENT') return { providers: {} };
    throw error;
  }
}

function writeConfigAtomic(configFile, config) {
  const dir = path.dirname(configFile);
  fs.mkdirSync(dir, { recursive: true });
  const tempFile = path.join(dir, `.${path.basename(configFile)}.${process.pid}.${Date.now()}.tmp`);
  try {
    fs.writeFileSync(tempFile, `${JSON.stringify(config, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
    fs.renameSync(tempFile, configFile);
  } finally {
    try { fs.unlinkSync(tempFile); } catch (_) {}
  }
}

function sameModels(left, right) {
  const ids = (models) => (models || []).map((model) => model.id).sort();
  return JSON.stringify(ids(left)) === JSON.stringify(ids(right));
}

function isFullProvider(provider) {
  return !!provider && typeof provider.name === 'string' && provider.name.length > 0
    && (Object.prototype.hasOwnProperty.call(provider, 'base-url')
      || Object.prototype.hasOwnProperty.call(provider, 'baseUrl'));
}

function applyFullProviderFromVps(provider, onDone, options = {}) {
  if (busy) {
    log.warn('pi-provider', 'provider operation already in progress; skipping apply');
    if (typeof onDone === 'function') onDone();
    return;
  }
  if (!isFullProvider(provider)) {
    log.warn('pi-provider', 'apply: invalid provider payload');
    if (typeof onDone === 'function') onDone();
    return;
  }
  const configFile = options.configFile || resolvePiModelsConfigFile();
  const fetch = options.fetchModels || fetchModels;
  const name = provider.name;
  const baseUrl = normalizeBaseUrl(provider['base-url'] || provider.baseUrl);
  const apiKey = provider['api-key'] != null ? String(provider['api-key']) : '';
  const currentOperation = ++operationId;
  busy = true;
  fetch(baseUrl, apiKey, (fetchError, fetchedModels) => {
    try {
      if (currentOperation !== operationId) return;
      const config = readConfig(configFile);
      if (!config.providers || typeof config.providers !== 'object' || Array.isArray(config.providers)) config.providers = {};
      const existing = config.providers[name] || {};
      const models = fetchError ? (Array.isArray(existing.models) ? existing.models : []) : fetchedModels;
      if (fetchError) log.warn('pi-provider', `model refresh failed; retaining existing list: ${fetchError.message}`);
      const next = { baseUrl, api: 'openai-responses', apiKey, models };
      if (JSON.stringify(existing) !== JSON.stringify(next)) {
        config.providers[name] = next;
        writeConfigAtomic(configFile, config);
        log.info('pi-provider', `provider updated: ${name} (${models.length} models)`);
      }
    } catch (error) {
      log.error('pi-provider', `apply failed: ${error.message}`);
    } finally {
      if (currentOperation === operationId) busy = false;
      if (typeof onDone === 'function') onDone();
    }
  });
}

function removeProviderByName(providerName, options = {}) {
  const name = String(providerName || '');
  if (!name) return;
  operationId += 1;
  busy = false;
  const configFile = options.configFile || resolvePiModelsConfigFile();
  let config;
  try { config = readConfig(configFile); } catch (error) {
    log.warn('pi-provider', `remove failed: ${error.message}`);
    return;
  }
  if (!config.providers || !Object.prototype.hasOwnProperty.call(config.providers, name)) return;
  delete config.providers[name];
  writeConfigAtomic(configFile, config);
  log.info('pi-provider', `provider removed: ${name}`);
}

function refreshModelsIfChanged(onDone, options = {}) {
  if (busy) {
    if (typeof onDone === 'function') onDone();
    return;
  }
  const configFile = options.configFile || resolvePiModelsConfigFile();
  let config;
  try { config = readConfig(configFile); } catch (error) {
    log.warn('pi-provider', `refresh failed: ${error.message}`);
    if (typeof onDone === 'function') onDone();
    return;
  }
  const name = Object.keys(config.providers || {})[0];
  if (!name) {
    if (typeof onDone === 'function') onDone();
    return;
  }
  const current = config.providers[name];
  const fetch = options.fetchModels || fetchModels;
  const currentOperation = ++operationId;
  busy = true;
  fetch(current.baseUrl, current.apiKey, (error, models) => {
    try {
      if (currentOperation !== operationId) return;
      if (error) log.warn('pi-provider', `model refresh failed: ${error.message}`);
      else if (!sameModels(current.models, models)) {
        current.models = models;
        writeConfigAtomic(configFile, config);
        log.info('pi-provider', `model list updated: ${name} (${models.length} models)`);
      }
    } catch (writeError) {
      log.error('pi-provider', `refresh failed: ${writeError.message}`);
    } finally {
      if (currentOperation === operationId) busy = false;
      if (typeof onDone === 'function') onDone();
    }
  });
}

module.exports = {
  applyFullProviderFromVps,
  fetchModels,
  isFullProvider,
  normalizeBaseUrl,
  refreshModelsIfChanged,
  removeProviderByName,
  resolvePiModelsConfigFile,
};
