import { join, isAbsolute, dirname } from 'path';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { randomBytes, randomUUID } from 'crypto';

const ROOT_DIR = dirname(fileURLToPath(import.meta.url));
const PROJECT_DIR = join(ROOT_DIR, '..');
const DEFAULT_WORKFLOW_RELATIVE = 'local-ai/workflows/vendoo_flux_schnell_api.json';

function toPosix(path) {
  return String(path || '').replace(/\\/g, '/');
}

function resolveWorkflowPath(pathValue = '') {
  const trimmed = String(pathValue || '').trim();
  if (!trimmed) return join(PROJECT_DIR, DEFAULT_WORKFLOW_RELATIVE);
  return isAbsolute(trimmed) ? trimmed : join(PROJECT_DIR, trimmed);
}

function getEnvBool(value, fallback = false) {
  if (value === undefined || value === null || value === '') return fallback;
  return !['0', 'false', 'no', 'off'].includes(String(value).toLowerCase());
}

export function generateLocalImageToken() {
  return randomBytes(24).toString('base64url');
}



export function getLocalImageConfig(settings = {}) {
  const workflowRelative = String(settings.ai_model_photo_local_workflow_path || process.env.LOCAL_IMAGE_WORKFLOW_PATH || DEFAULT_WORKFLOW_RELATIVE).trim() || DEFAULT_WORKFLOW_RELATIVE;
  const workflowPath = resolveWorkflowPath(workflowRelative);
  return {
    enabled: getEnvBool(settings.ai_model_photo_local_enabled ?? process.env.LOCAL_IMAGE_ENABLED, true),
    url: String(settings.ai_model_photo_local_url || process.env.LOCAL_IMAGE_URL || 'http://127.0.0.1:8188').trim().replace(/\/$/, ''),
    token: String(settings.ai_model_photo_local_token || process.env.LOCAL_IMAGE_TOKEN || '').trim(),
    workflow_path: workflowPath,
    workflow_relative: toPosix(workflowRelative),
    install_path: String(settings.ai_model_photo_local_install_path || process.env.LOCAL_IMAGE_INSTALL_PATH || '').trim(),
    require_token: getEnvBool(settings.ai_model_photo_local_require_token ?? process.env.LOCAL_IMAGE_REQUIRE_TOKEN, true),
    allow_remote: getEnvBool(process.env.LOCAL_IMAGE_ALLOW_REMOTE, false),
  };
}

function assertSafeLocalUrl(config) {
  let parsed;
  try { parsed = new URL(config.url); } catch { throw new Error('Ungültige lokale Bildserver-URL.'); }
  const host = String(parsed.hostname || '').toLowerCase();
  const loopback = ['127.0.0.1', 'localhost', '::1'].includes(host);
  if (!loopback && !config.allow_remote) {
    throw new Error('Aus Sicherheitsgründen sind für lokalen FLUX standardmäßig nur localhost/127.0.0.1 erlaubt.');
  }
  return { loopback, host };
}

function getAuthHeaders(config) {
  const headers = {};
  if (config.token) {
    headers['Authorization'] = `Bearer ${config.token}`;
    headers['X-API-Key'] = config.token;
  }
  return headers;
}

function getHeaders(config) {
  return { ...getAuthHeaders(config), 'Content-Type': 'application/json' };
}


async function fetchWithTimeout(url, options = {}, timeoutMs = 15000) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
  try {
    return await fetch(url, { ...options, signal: controller.signal });
  } finally {
    clearTimeout(timeout);
  }
}

function replaceDeep(value, replacements) {
  if (typeof value === 'string') {
    let out = value;
    for (const [key, val] of Object.entries(replacements)) {
      out = out.split(key).join(String(val));
    }
    return out;
  }
  if (Array.isArray(value)) return value.map(item => replaceDeep(item, replacements));
  if (value && typeof value === 'object') {
    return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, replaceDeep(v, replacements)]));
  }
  return value;
}

function firstImageFromHistory(history = {}) {
  for (const node of Object.values(history?.outputs || {})) {
    if (Array.isArray(node?.images) && node.images.length) return node.images[0];
  }
  return null;
}

async function pollHistory(config, promptId, timeoutMs = 300000, onProgress = null, { onPhase = null, isCancelled = null } = {}) {
  const started = Date.now();
  let lastPayload = null;
  while (Date.now() - started < timeoutMs) {
    if (typeof isCancelled === 'function' && await isCancelled()) {
      const error = new Error('FLUX-Auftrag wurde abgebrochen.');
      error.code = 'FLUX_CANCELLED';
      throw error;
    }
    const elapsedSeconds = Math.max(0, Math.round((Date.now() - started) / 1000));
    const message = elapsedSeconds < 8
      ? 'ComfyUI übernimmt den Auftrag …'
      : `FLUX rendert das Bild seit ${elapsedSeconds} Sekunden …`;
    if (typeof onPhase === 'function') onPhase('rendering', message, { elapsed_seconds: elapsedSeconds });
    if (typeof onProgress === 'function') {
      const estimated = Math.min(88, 24 + Math.round((elapsedSeconds / 150) * 60));
      onProgress(estimated, message);
    }
    const response = await fetchWithTimeout(`${config.url}/history/${promptId}`, { headers: getHeaders(config) }, 12000);
    const payload = await response.json().catch(() => ({}));
    lastPayload = payload?.[promptId] || payload;
    const image = firstImageFromHistory(lastPayload);
    if (image) return { history: lastPayload, image };
    await new Promise(resolve => setTimeout(resolve, 2000));
  }
  throw new Error('Lokale FLUX/ComfyUI-Generierung hat nach fünf Minuten das Zeitlimit überschritten.');
}

async function fetchComfyImageAsBase64(config, imageInfo) {
  const params = new URLSearchParams({ filename: imageInfo.filename, subfolder: imageInfo.subfolder || '', type: imageInfo.type || 'output' });
  const response = await fetchWithTimeout(`${config.url}/view?${params.toString()}`, { headers: getHeaders(config) }, 60000);
  if (!response.ok) throw new Error(`ComfyUI-Bildabruf fehlgeschlagen (${response.status})`);
  return Buffer.from(await response.arrayBuffer()).toString('base64');
}

function parseDataUrl(dataUrl = '') {
  const match = String(dataUrl || '').match(/^data:([^;,]+);base64,(.+)$/s);
  if (!match) return null;
  return { mime: match[1] || 'image/jpeg', buffer: Buffer.from(match[2], 'base64') };
}

async function uploadSourceImage(config, sourceDataUrl, onProgress = null) {
  const parsed = parseDataUrl(sourceDataUrl);
  if (!parsed?.buffer?.length) return null;
  if (typeof onProgress === 'function') onProgress(8, 'Produktfotoreferenz wird an ComfyUI übertragen …');
  const extension = parsed.mime.includes('png') ? 'png' : parsed.mime.includes('webp') ? 'webp' : 'jpg';
  const filename = `vendoo-reference-${randomUUID()}.${extension}`;
  const form = new FormData();
  form.append('image', new Blob([parsed.buffer], { type: parsed.mime }), filename);
  form.append('type', 'input');
  form.append('overwrite', 'true');
  const response = await fetchWithTimeout(`${config.url}/upload/image`, {
    method: 'POST',
    headers: getAuthHeaders(config),
    body: form,
  }, 60000);
  const payload = await response.json().catch(() => ({}));
  if (!response.ok || !payload?.name) {
    const detail = payload?.error || payload?.message || `HTTP ${response.status}`;
    throw new Error(`Produktfotoreferenz konnte nicht an ComfyUI übertragen werden: ${detail}`);
  }
  const imageName = payload.subfolder ? `${payload.subfolder}/${payload.name}` : payload.name;
  return { imageName, payload };
}

function applySourceImageConditioning(workflow, imageName, mode = 'model', referenceStrength = 70, resolution = 768) {
  if (!imageName || !workflow?.['7']?.inputs || !workflow?.['3']) return { workflow, used: false, denoise: 1 };
  const strength = Math.max(30, Math.min(90, Number(referenceStrength || 70)));
  const targetSize = Number(resolution) === 1024 ? 1024 : 768;
  const baseDenoise = mode === 'flatlay' ? 0.52 : mode === 'ghost_mannequin' ? 0.64 : 0.80;
  const denoise = Math.max(0.42, Math.min(0.88, Number((baseDenoise - ((strength - 60) * 0.0035)).toFixed(3))));
  workflow['10'] = { inputs: { image: imageName, upload: 'image' }, class_type: 'LoadImage' };
  workflow['11'] = {
    inputs: { upscale_method: 'lanczos', width: targetSize, height: targetSize, crop: 'disabled', image: ['10', 0] },
    class_type: 'ImageScale',
  };
  workflow['12'] = { inputs: { pixels: ['11', 0], vae: ['3', 2] }, class_type: 'VAEEncode' };
  workflow['7'].inputs.latent_image = ['12', 0];
  workflow['7'].inputs.denoise = denoise;
  return { workflow, used: true, denoise };
}

export async function getLocalImageStatus(settings = {}) {
  const config = getLocalImageConfig(settings);
  const workflowExists = existsSync(config.workflow_path);
  const configuredInstallRoot = config.install_path
    ? (isAbsolute(config.install_path) ? config.install_path : join(PROJECT_DIR, config.install_path))
    : join(PROJECT_DIR, 'local-ai', 'comfyui');
  const portableRoot = existsSync(join(configuredInstallRoot, 'ComfyUI', 'main.py'))
    ? configuredInstallRoot
    : join(configuredInstallRoot, 'ComfyUI_windows_portable');
  const comfyMain = join(portableRoot, 'ComfyUI', 'main.py');
  const modelPath = join(portableRoot, 'ComfyUI', 'models', 'checkpoints', 'flux1-schnell-fp8.safetensors');
  const status = {
    enabled: config.enabled,
    url: config.url,
    token_set: !!config.token,
    require_token: config.require_token,
    workflow_path: config.workflow_relative,
    workflow_exists: workflowExists,
    install_path: config.install_path,
    comfy_installed: existsSync(comfyMain),
    model_installed: existsSync(modelPath),
    model_path: modelPath,
    reachable: false,
    installed: existsSync(comfyMain),
    localhost_only: !config.allow_remote,
    reference_image_supported: true,
    message: '',
  };
  try {
    const security = assertSafeLocalUrl(config);
    status.loopback = security.loopback;
    const res = await fetchWithTimeout(`${config.url}/system_stats`, { headers: getHeaders(config) }, 8000);
    status.reachable = res.ok;
    status.installed = res.ok;
    const payload = await res.json().catch(() => ({}));
    status.system = payload?.system || payload || null;
    status.message = res.ok
      ? `Lokaler Bildserver erreichbar${workflowExists ? '' : ' · Workflow-Datei fehlt noch'}.`
      : `Lokaler Bildserver antwortet mit HTTP ${res.status}.`;
  } catch (error) {
    status.message = `Lokaler Bildserver nicht erreichbar: ${error.message || error}`;
  }
  return status;
}

export async function generateWithLocalFlux({
  prompt,
  settings = {},
  mode = 'model',
  variantIndex = 0,
  sourceDataUrl = '',
  onProgress = null,
  onPhase = null,
  onSubmitted = null,
  isCancelled = null,
  width = null,
  height = null,
  seed = null,
  steps = null,
}) {
  const config = getLocalImageConfig(settings);
  const throwIfCancelled = async () => {
    if (typeof isCancelled === 'function' && await isCancelled()) {
      const error = new Error('FLUX-Auftrag wurde abgebrochen.');
      error.code = 'FLUX_CANCELLED';
      throw error;
    }
  };
  await throwIfCancelled();
  if (!config.enabled) throw new Error('Lokale FLUX-Bildgenerierung ist deaktiviert.');
  assertSafeLocalUrl(config);
  if (!existsSync(config.workflow_path)) throw new Error(`Workflow-Datei nicht gefunden: ${config.workflow_relative}`);
  if (typeof onPhase === 'function') onPhase('preparing', 'Lokaler FLUX-Workflow wird vorbereitet …');
  if (typeof onProgress === 'function') onProgress(3, 'Lokaler FLUX-Workflow wird vorbereitet …');
  const workflowText = readFileSync(config.workflow_path, 'utf8').replace(/^﻿/, '');
  let raw;
  try {
    raw = JSON.parse(workflowText);
  } catch (error) {
    throw new Error(`Lokaler FLUX-Workflow ist kein gültiges JSON: ${error.message || error}`);
  }
  const replacements = {
    '__PROMPT__': prompt,
    '__NEGATIVE_PROMPT__': mode === 'prompt' ? '' : 'nsfw, nudity, lingerie, child, low quality, extra limbs, cropped garment, wrong garment, different clothing, changed logo, changed color',
    '__SEED__': Number.isFinite(Number(seed)) ? Math.max(0, Math.min(2147483647, Number(seed))) : Math.floor(Math.random() * 2147483647),
    '__MODE__': mode,
    '__VARIANT_INDEX__': variantIndex + 1,
    '__SOURCE_IMAGE__': sourceDataUrl || '',
    '__CLIENT_ID__': 'vendoo',
  };
  let workflow = replaceDeep(raw, replacements);
  if (workflow?.['7']?.inputs) workflow['7'].inputs.seed = replacements.__SEED__;
  const resolution = Number(settings.ai_model_photo_local_resolution) === 1024 ? 1024 : 768;
  const targetWidth = Number(width) > 0 ? Math.max(256, Math.min(2048, Math.round(Number(width) / 8) * 8)) : resolution;
  const targetHeight = Number(height) > 0 ? Math.max(256, Math.min(2048, Math.round(Number(height) / 8) * 8)) : resolution;
  if (workflow?.['6']?.inputs) {
    workflow['6'].inputs.width = targetWidth;
    workflow['6'].inputs.height = targetHeight;
  }
  const targetSteps = Number.isFinite(Number(steps)) ? Math.max(4, Math.min(20, Math.round(Number(steps)))) : null;
  if (targetSteps && workflow?.['7']?.inputs) workflow['7'].inputs.steps = targetSteps;
  if (mode === 'prompt' && workflow?.['7']?.inputs) {
    workflow['7'].inputs.cfg = 1;
    workflow['7'].inputs.sampler_name = 'euler';
    workflow['7'].inputs.scheduler = 'simple';
    workflow['7'].inputs.denoise = 1;
  }
  const useReference = getEnvBool(settings.ai_model_photo_use_source_reference, true) && !!sourceDataUrl;
  let reference = { used: false, denoise: 1 };
  if (useReference) {
    const uploaded = await uploadSourceImage(config, sourceDataUrl, onProgress);
    if (uploaded?.imageName) reference = applySourceImageConditioning(workflow, uploaded.imageName, mode, settings.ai_model_photo_reference_strength || 70, resolution);
  }
  if (typeof onPhase === 'function') onPhase('submitting', reference.used ? 'Produktfotoreferenz ist eingebunden.' : 'Text-Workflow ist vorbereitet.');
  if (typeof onProgress === 'function') onProgress(16, reference.used ? 'Produktfotoreferenz ist eingebunden.' : 'Text-Workflow ist vorbereitet.');
  await throwIfCancelled();
  const submit = await fetchWithTimeout(`${config.url}/prompt`, {
    method: 'POST',
    headers: getHeaders(config),
    body: JSON.stringify({ client_id: `vendoo-${randomUUID()}`, prompt: workflow }),
  }, 30000);
  const submitPayload = await submit.json().catch(() => ({}));
  if (!submit.ok || !submitPayload.prompt_id) {
    const message = submitPayload?.error?.message || submitPayload?.error || submitPayload?.message || `HTTP ${submit.status}`;
    throw new Error(`ComfyUI-Prompt fehlgeschlagen: ${message}`);
  }
  if (typeof onSubmitted === 'function') await onSubmitted(submitPayload.prompt_id);
  try {
    await throwIfCancelled();
  } catch (error) {
    try { await cancelLocalFluxPrompt(submitPayload.prompt_id, settings); } catch {}
    throw error;
  }
  if (typeof onPhase === 'function') onPhase('submitted', 'Auftrag wurde an FLUX übergeben …', { prompt_id: submitPayload.prompt_id });
  if (typeof onProgress === 'function') onProgress(22, 'Auftrag wurde an FLUX übergeben …');
  const done = await pollHistory(config, submitPayload.prompt_id, 300000, onProgress, { onPhase, isCancelled });
  if (typeof onPhase === 'function') onPhase('downloading', 'Fertiges Bild wird von ComfyUI geladen …');
  if (typeof onProgress === 'function') onProgress(92, 'Fertiges Bild wird von ComfyUI geladen …');
  const base64 = await fetchComfyImageAsBase64(config, done.image);
  if (typeof onPhase === 'function') onPhase('completed', 'Lokales FLUX-Bild wurde erzeugt.');
  if (typeof onProgress === 'function') onProgress(100, 'Lokales FLUX-Bild wurde erzeugt.');
  return {
    base64,
    model: 'flux-schnell-local',
    provider: 'local',
    label: 'Lokales FLUX / ComfyUI',
    free: true,
    prompt_id: submitPayload.prompt_id,
    reference_used: reference.used,
    reference_denoise: reference.denoise,
    resolution,
    width: targetWidth,
    height: targetHeight,
    seed: replacements.__SEED__,
    steps: targetSteps || workflow?.['7']?.inputs?.steps || 6,
  };
}


export async function waitForLocalFluxPrompt({ promptId, settings = {}, timeoutMs = 300000, onPhase = null, isCancelled = null } = {}) {
  const config = getLocalImageConfig(settings);
  if (!promptId) throw new Error('ComfyUI-Prompt-ID fehlt.');
  assertSafeLocalUrl(config);
  const done = await pollHistory(config, String(promptId), timeoutMs, null, { onPhase, isCancelled });
  if (typeof onPhase === 'function') onPhase('downloading', 'Fertiges Bild wird von ComfyUI geladen …');
  const base64 = await fetchComfyImageAsBase64(config, done.image);
  return { base64, prompt_id: String(promptId), history: done.history, image: done.image };
}

export async function interruptLocalFlux(settings = {}) {
  const config = getLocalImageConfig(settings);
  assertSafeLocalUrl(config);
  const response = await fetchWithTimeout(`${config.url}/interrupt`, {
    method: 'POST',
    headers: getHeaders(config),
    body: JSON.stringify({}),
  }, 15000);
  const payload = await response.json().catch(() => ({}));
  if (!response.ok) throw new Error(payload?.error || payload?.message || `ComfyUI-Abbruch fehlgeschlagen (${response.status})`);
  return { ok: true, payload };
}

function comfyQueuePromptIds(items = []) {
  return (Array.isArray(items) ? items : [])
    .map(item => Array.isArray(item) ? item[1] : item?.prompt_id || item?.id)
    .filter(Boolean)
    .map(String);
}

export async function cancelLocalFluxPrompt(promptId, settings = {}) {
  const id = String(promptId || '').trim();
  if (!id) return { ok: false, mode: 'not_submitted' };
  const config = getLocalImageConfig(settings);
  assertSafeLocalUrl(config);

  const queueResponse = await fetchWithTimeout(`${config.url}/queue`, { headers: getHeaders(config) }, 12000);
  const queue = await queueResponse.json().catch(() => ({}));
  if (!queueResponse.ok) throw new Error(queue?.error || queue?.message || `ComfyUI-Queue konnte nicht gelesen werden (${queueResponse.status})`);

  const runningIds = comfyQueuePromptIds(queue?.queue_running);
  const pendingIds = comfyQueuePromptIds(queue?.queue_pending);
  if (pendingIds.includes(id)) {
    const response = await fetchWithTimeout(`${config.url}/queue`, {
      method: 'POST',
      headers: getHeaders(config),
      body: JSON.stringify({ delete: [id] }),
    }, 15000);
    const payload = await response.json().catch(() => ({}));
    if (!response.ok) throw new Error(payload?.error || payload?.message || `ComfyUI-Warteschlangenauftrag konnte nicht entfernt werden (${response.status})`);
    return { ok: true, mode: 'dequeued', prompt_id: id, payload };
  }
  if (runningIds.includes(id)) {
    const interrupted = await interruptLocalFlux(settings);
    return { ...interrupted, mode: 'interrupted', prompt_id: id };
  }
  return { ok: true, mode: 'already_finished_or_missing', prompt_id: id };
}

export async function getLocalFluxRuntimeInfo(settings = {}, { historyLimit = 20 } = {}) {
  const config = getLocalImageConfig(settings);
  assertSafeLocalUrl(config);
  const fetchJson = async (path, timeout = 12000) => {
    const response = await fetchWithTimeout(`${config.url}${path}`, { headers: getHeaders(config) }, timeout);
    const payload = await response.json().catch(() => ({}));
    if (!response.ok) throw new Error(`${path} antwortet mit HTTP ${response.status}`);
    return payload;
  };
  const [systemResult, queueResult, historyResult] = await Promise.allSettled([
    fetchJson('/system_stats'),
    fetchJson('/queue'),
    fetchJson(`/history?max_items=${Math.max(1, Math.min(100, Number(historyLimit) || 20))}`),
  ]);
  const systemStats = systemResult.status === 'fulfilled' ? systemResult.value : null;
  const queue = queueResult.status === 'fulfilled' ? queueResult.value : null;
  const history = historyResult.status === 'fulfilled' ? historyResult.value : null;
  const running = Array.isArray(queue?.queue_running) ? queue.queue_running.length : 0;
  const pending = Array.isArray(queue?.queue_pending) ? queue.queue_pending.length : 0;
  const historyItems = history && typeof history === 'object' ? Object.entries(history).slice(0, Math.max(1, Number(historyLimit) || 20)) : [];
  return {
    reachable: !!systemStats || !!queue || !!history,
    url: config.url,
    system_stats: systemStats,
    queue,
    history: Object.fromEntries(historyItems),
    queue_summary: { running, pending, total: running + pending },
    errors: {
      system_stats: systemResult.status === 'rejected' ? systemResult.reason?.message || String(systemResult.reason) : null,
      queue: queueResult.status === 'rejected' ? queueResult.reason?.message || String(queueResult.reason) : null,
      history: historyResult.status === 'rejected' ? historyResult.reason?.message || String(historyResult.reason) : null,
    },
  };
}


export function ensureLocalFluxBootstrap({ token = '', installPath = '' } = {}) {
  const localDir = join(PROJECT_DIR, 'local-ai');
  const workflowsDir = join(localDir, 'workflows');
  mkdirSync(workflowsDir, { recursive: true });
  const workflowPath = join(workflowsDir, 'vendoo_flux_schnell_api.json');
  if (!existsSync(workflowPath)) {
    const workflow = {
      '3': { inputs: { ckpt_name: 'flux1-schnell-fp8.safetensors' }, class_type: 'CheckpointLoaderSimple' },
      '4': { inputs: { text: '__PROMPT__', clip: ['3', 1] }, class_type: 'CLIPTextEncode' },
      '5': { inputs: { text: '__NEGATIVE_PROMPT__', clip: ['3', 1] }, class_type: 'CLIPTextEncode' },
      '6': { inputs: { width: 1024, height: 1024, batch_size: 1 }, class_type: 'EmptyLatentImage' },
      '7': { inputs: { seed: '__SEED__', steps: 6, cfg: 1, sampler_name: 'euler', scheduler: 'simple', denoise: 1, model: ['3', 0], positive: ['4', 0], negative: ['5', 0], latent_image: ['6', 0] }, class_type: 'KSampler' },
      '8': { inputs: { samples: ['7', 0], vae: ['3', 2] }, class_type: 'VAEDecode' },
      '9': { inputs: { filename_prefix: 'Vendoo/flux_prompt', images: ['8', 0] }, class_type: 'SaveImage' },
    };
    writeFileSync(workflowPath, JSON.stringify(workflow, null, 2), 'utf8');
  }
  const envLocalPath = join(localDir, '.env.local-flux');
  if (!existsSync(envLocalPath)) {
    writeFileSync(envLocalPath, [
      'LOCAL_IMAGE_URL=http://127.0.0.1:8188',
      `LOCAL_IMAGE_TOKEN=${token || generateLocalImageToken()}`,
      `LOCAL_IMAGE_WORKFLOW_PATH=${DEFAULT_WORKFLOW_RELATIVE}`,
      `LOCAL_IMAGE_INSTALL_PATH=${installPath}`,
    ].join('\n') + '\n', 'utf8');
  }
  return { workflow_path: toPosix(DEFAULT_WORKFLOW_RELATIVE), env_file: toPosix('local-ai/.env.local-flux') };
}
