const fs = require('fs');
const path = require('path');
const http = require('http');
const { spawn, execFile } = require('child_process');
const { promisify } = require('util');

const { ensureDir, getDataDir } = require('../config/runtime');
const { LOCAL_RUNTIME_MODELS, getLocalRuntimeModel } = require('../config/local-runtime-models');

const execFileAsync = promisify(execFile);
const EMBEDDED_RUNTIME_PORT = 11436;
const EMBEDDED_RUNTIME_HOST = '127.0.0.1';

function createEmbeddedRuntimeService() {
  const runtimeDir = ensureDir(path.join(getDataDir(), 'runtime'));
  const venvDir = path.join(runtimeDir, 'venv');
  const modelsDir = ensureDir(path.join(runtimeDir, 'models'));
  const logsDir = ensureDir(path.join(runtimeDir, 'logs'));
  const statePath = path.join(runtimeDir, 'runtime-state.json');
  const serverScriptPath = path.join(process.cwd(), 'desktop', 'runtime', 'embedded_runtime_server.py');

  function isSupportedMachine() {
    return process.platform === 'darwin' && process.arch === 'arm64';
  }

  function getVenvPython() {
    return path.join(venvDir, 'bin', 'python3');
  }

  function hasVenv() {
    return fs.existsSync(getVenvPython());
  }

  function getModelPath(slug) {
    return path.join(modelsDir, slug);
  }

  function readRuntimeState() {
    if (!fs.existsSync(statePath)) return {};
    try {
      return JSON.parse(fs.readFileSync(statePath, 'utf8')) || {};
    } catch {
      return {};
    }
  }

  function writeRuntimeState(patch = {}) {
    const nextState = { ...readRuntimeState(), ...patch };
    fs.writeFileSync(statePath, JSON.stringify(nextState, null, 2));
    return nextState;
  }

  function wait(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  async function ensureVenv() {
    if (hasVenv()) return getVenvPython();
    await execFileAsync(process.env.ORG_BOARD_PYTHON_BIN || 'python3', ['-m', 'venv', venvDir], { maxBuffer: 16 * 1024 * 1024 });
    return getVenvPython();
  }

  async function ensurePackages() {
    const python = await ensureVenv();
    try {
      await execFileAsync(python, ['-m', 'pip', 'install', '--upgrade', 'pip'], { maxBuffer: 16 * 1024 * 1024 });
      await execFileAsync(python, ['-m', 'pip', 'install', 'mlx==0.31.1', 'mlx-lm==0.31.1', 'huggingface_hub'], { maxBuffer: 32 * 1024 * 1024 });
      writeRuntimeState({ pythonReady: true, packagesReady: true, lastError: null });
      return python;
    } catch (error) {
      writeRuntimeState({
        pythonReady: hasVenv(),
        packagesReady: false,
        lastError: error.stderr || error.stdout || error.message || 'Local runtime package install failed'
      });
      throw error;
    }
  }

  async function isServerHealthy() {
    return new Promise((resolve) => {
      const req = http.get(`http://${EMBEDDED_RUNTIME_HOST}:${EMBEDDED_RUNTIME_PORT}/health`, (res) => {
        let data = '';
        res.on('data', (chunk) => { data += chunk; });
        res.on('end', () => {
          try {
            const payload = JSON.parse(data || '{}');
            resolve(res.statusCode === 200 ? payload : null);
          } catch {
            resolve(null);
          }
        });
      });
      req.on('error', () => resolve(null));
      req.setTimeout(1500, () => {
        req.destroy();
        resolve(null);
      });
    });
  }

  async function downloadModel(modelSlug) {
    const model = getLocalRuntimeModel(modelSlug);
    if (!model) throw new Error(`Unknown embedded model: ${modelSlug}`);
    const modelPath = getModelPath(model.slug);
    if (fs.existsSync(path.join(modelPath, 'config.json')) || fs.existsSync(path.join(modelPath, 'tokenizer.json'))) {
      writeRuntimeState({
        downloadedModelSlugs: Array.from(new Set([...(readRuntimeState().downloadedModelSlugs || []), model.slug])),
        lastError: null
      });
      return { ok: true, modelSlug: model.slug, alreadyInstalled: true, modelPath, repo: model.huggingFaceRepo };
    }
    ensureDir(modelPath);
    const python = await ensurePackages();
    const code = `
from huggingface_hub import snapshot_download
snapshot_download(
    repo_id=${JSON.stringify(model.huggingFaceRepo)},
    local_dir=${JSON.stringify(modelPath)},
    local_dir_use_symlinks=False
)
print("done")
`;
    await execFileAsync(python, ['-c', code], { maxBuffer: 32 * 1024 * 1024 });
    writeRuntimeState({
      downloadedModelSlugs: Array.from(new Set([...(readRuntimeState().downloadedModelSlugs || []), model.slug])),
      lastError: null
    });
    return { ok: true, modelSlug: model.slug, alreadyInstalled: false, modelPath, repo: model.huggingFaceRepo };
  }

  async function ensureServer(modelSlug) {
    const model = getLocalRuntimeModel(modelSlug);
    if (!model) throw new Error(`Unknown embedded model: ${modelSlug}`);
    const modelPath = getModelPath(model.slug);
    if (!fs.existsSync(modelPath)) throw new Error(`Embedded model ${model.slug} is not downloaded yet`);

    const healthy = await isServerHealthy();
    if (healthy?.model_path === modelPath) {
      writeRuntimeState({ activeModelSlug: model.slug, activeModelPath: modelPath, running: true });
      return { ok: true, alreadyRunning: true, modelSlug: model.slug, modelPath };
    }

    const python = await ensurePackages();
    const logPath = path.join(logsDir, 'embedded-runtime.log');
    const child = spawn(python, [serverScriptPath, '--host', EMBEDDED_RUNTIME_HOST, '--port', String(EMBEDDED_RUNTIME_PORT), '--model-path', modelPath], {
      detached: true,
      stdio: ['ignore', fs.openSync(logPath, 'a'), fs.openSync(logPath, 'a')]
    });
    child.unref();

    const startedAt = Date.now();
    while (Date.now() - startedAt < 30000) {
      const running = await isServerHealthy();
      if (running?.model_path === modelPath) {
        writeRuntimeState({ activeModelSlug: model.slug, activeModelPath: modelPath, running: true });
        return { ok: true, alreadyRunning: false, modelSlug: model.slug, modelPath };
      }
      await wait(750);
    }

    throw new Error('Embedded runtime did not start within 30 seconds');
  }

  async function chat({ messages, modelSlug, maxTokens = 700, temperature = 0.2 }) {
    const targetSlug = modelSlug || readRuntimeState().activeModelSlug || Object.keys(LOCAL_RUNTIME_MODELS)[0];
    await ensureServer(targetSlug);
    const payload = JSON.stringify({ messages, max_tokens: maxTokens, temperature });
    return new Promise((resolve, reject) => {
      const req = http.request({
        host: EMBEDDED_RUNTIME_HOST,
        port: EMBEDDED_RUNTIME_PORT,
        path: '/chat',
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Content-Length': Buffer.byteLength(payload)
        }
      }, (res) => {
        let data = '';
        res.on('data', (chunk) => { data += chunk; });
        res.on('end', () => {
          try {
            const body = JSON.parse(data || '{}');
            if (res.statusCode !== 200) return reject(new Error(body.error || 'Embedded runtime chat failed'));
            resolve(body.content || '');
          } catch (error) {
            reject(error);
          }
        });
      });
      req.on('error', reject);
      req.write(payload);
      req.end();
    });
  }

  async function embed({ text, modelSlug }) {
    const targetSlug = modelSlug || readRuntimeState().activeModelSlug || Object.keys(LOCAL_RUNTIME_MODELS)[0];
    await ensureServer(targetSlug);
    const payload = JSON.stringify({ text });
    return new Promise((resolve, reject) => {
      const req = http.request({
        host: EMBEDDED_RUNTIME_HOST,
        port: EMBEDDED_RUNTIME_PORT,
        path: '/embed',
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Content-Length': Buffer.byteLength(payload)
        }
      }, (res) => {
        let data = '';
        res.on('data', (chunk) => { data += chunk; });
        res.on('end', () => {
          try {
            const body = JSON.parse(data || '{}');
            if (res.statusCode !== 200) return reject(new Error(body.error || 'Embedded runtime embedding failed'));
            resolve(body.embedding || null);
          } catch (error) {
            reject(error);
          }
        });
      });
      req.on('error', reject);
      req.write(payload);
      req.end();
    });
  }

  async function getStatus() {
    const runtimeState = readRuntimeState();
    const installedModels = Object.values(LOCAL_RUNTIME_MODELS)
      .filter((model) => fs.existsSync(getModelPath(model.slug)))
      .map((model) => ({ slug: model.slug, label: model.label, repo: model.huggingFaceRepo }));
    const healthy = await isServerHealthy();
    const pythonReady = hasVenv() || Boolean(runtimeState.pythonReady);
    let packagesReady = false;
    if (runtimeState.packagesReady) {
      packagesReady = true;
    } else if (pythonReady) {
      try {
        await execFileAsync(getVenvPython(), ['-c', 'import mlx, mlx_lm, huggingface_hub'], { maxBuffer: 16 * 1024 * 1024 });
        packagesReady = true;
        writeRuntimeState({ pythonReady: true, packagesReady: true, lastError: null });
      } catch (_) {}
    }
    return {
      supported: isSupportedMachine(),
      pythonReady,
      packagesReady,
      serverRunning: Boolean(healthy),
      activeModelSlug: runtimeState.activeModelSlug || null,
      installedModels,
      catalog: Object.values(LOCAL_RUNTIME_MODELS),
      port: EMBEDDED_RUNTIME_PORT,
      lastError: runtimeState.lastError || null
    };
  }

  return {
    getStatus,
    ensurePackages,
    downloadModel,
    ensureServer,
    chat,
    embed,
    isSupportedMachine
  };
}

module.exports = { createEmbeddedRuntimeService };
