import { randomUUID } from 'node:crypto';
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { readRuntimeConfig, setConfigValue, writeRuntimeConfig } from '../../../lib/config-store.mjs';
import { getSecret, getSecretMetadata, setSecret } from '../../../lib/secret-store.mjs';
import {
  APP_ROOT, APP_VERSION, BACKUP_DIR, DATA_ROOT, DB_PATH, LOG_DIR,
  MODULES_DIR, MODULE_STATE_PATH, OPERATIONS_DIR, UPLOADS_DIR,
  runtimePathSummary, verifyRuntimeWritable,
} from '../../../lib/runtime-paths.mjs';
import { db } from '../../../lib/db.mjs';
import { PlatformError } from '../../kernel/errors.mjs';
import { deploymentRepository } from './repository.mjs';

const PROVIDERS = new Set(['none', 'coolify']);
const MODES = new Set(['webhook', 'api']);
const CHANNELS = new Set(['stable', 'beta']);
const METHODS = new Set(['GET', 'POST']);
const TERMINAL_COOLIFY_SUCCESS = new Set(['finished', 'success', 'succeeded', 'completed', 'healthy']);
const TERMINAL_COOLIFY_FAILURE = new Set(['failed', 'error', 'cancelled', 'canceled', 'stopped']);
const MASKED_SECRET = '••••••••';

function fail(message, code = 'DEPLOYMENT_CONFIGURATION_INVALID', status = 400, details = null) {
  throw new PlatformError(message, { code, status, expose: true, details });
}

function clean(value, max = 1000) {
  const text = String(value ?? '').trim();
  if (text.length > max) fail('Deployment-Einstellung ist zu lang.');
  if (/\r|\n|\0/.test(text)) fail('Deployment-Einstellung enthält unzulässige Steuerzeichen.');
  return text;
}

function cleanUuid(value) {
  const text = clean(value, 120);
  if (text && !/^[a-zA-Z0-9_-]{6,120}$/.test(text)) fail('Coolify Resource UUID besitzt ein ungültiges Format.');
  return text;
}

function cleanIdempotencyKey(value) {
  const text = clean(value || randomUUID(), 160);
  if (!/^[a-zA-Z0-9._:-]{8,160}$/.test(text)) fail('Idempotency-Key besitzt ein ungültiges Format.', 'IDEMPOTENCY_KEY_INVALID');
  return text;
}

function normalizeBaseUrl(value) {
  const text = clean(value, 1000).replace(/\/$/, '');
  if (!text) return '';
  let url;
  try { url = new URL(text); } catch { fail('Coolify URL ist ungültig.'); }
  if (!['https:', 'http:'].includes(url.protocol)) fail('Coolify URL muss HTTP oder HTTPS verwenden.');
  if (url.username || url.password || url.search || url.hash) fail('Coolify URL darf keine Zugangsdaten, Query oder Fragment enthalten.');
  if (process.env.NODE_ENV === 'production' && url.protocol !== 'https:' && !/^(1|true|yes)$/i.test(String(process.env.VENDOO_ALLOW_INSECURE_COOLIFY || 'false'))) {
    fail('In Produktion muss die Coolify URL HTTPS verwenden.');
  }
  return url.toString().replace(/\/$/, '');
}

function normalizeWebhookUrl(value) {
  const text = clean(value, 2000);
  if (!text) return '';
  let url;
  try { url = new URL(text); } catch { fail('Coolify Deploy-Webhook ist ungültig.'); }
  if (!['https:', 'http:'].includes(url.protocol)) fail('Coolify Deploy-Webhook muss HTTP oder HTTPS verwenden.');
  if (url.username || url.password || url.hash) fail('Coolify Deploy-Webhook darf keine URL-Zugangsdaten oder Fragmente enthalten.');
  if (process.env.NODE_ENV === 'production' && url.protocol !== 'https:' && !/^(1|true|yes)$/i.test(String(process.env.VENDOO_ALLOW_INSECURE_COOLIFY || 'false'))) {
    fail('In Produktion muss der Coolify Deploy-Webhook HTTPS verwenden.');
  }
  return url.toString();
}

function normalizeEndpoint(value, fallback, placeholders = []) {
  const text = clean(value || fallback, 500);
  if (!text.startsWith('/') || text.startsWith('//') || text.includes('..')) fail('Coolify API-Endpunkt muss ein sicherer relativer Pfad sein.');
  if (/\r|\n|\0/.test(text)) fail('Coolify API-Endpunkt ist ungültig.');
  const unknown = [...text.matchAll(/\{([^}]+)\}/g)].map(match => match[1]).filter(name => !placeholders.includes(name));
  if (unknown.length) fail(`Unbekannter Platzhalter im Coolify-Endpunkt: ${unknown.join(', ')}`);
  return text;
}

function boolEnv(name, fallback = false) {
  const raw = process.env[name];
  if (raw === undefined || raw === '') return fallback;
  return /^(1|true|yes|on)$/i.test(String(raw));
}

function intEnv(name, fallback, min, max) {
  const value = Number(process.env[name]);
  return Number.isFinite(value) ? Math.max(min, Math.min(max, Math.trunc(value))) : fallback;
}

function buildRevision() {
  return clean(process.env.VENDOO_BUILD_REVISION || process.env.GIT_COMMIT || process.env.SOURCE_COMMIT || '', 160) || null;
}

function currentSettings() {
  const provider = PROVIDERS.has(process.env.VENDOO_DEPLOY_PROVIDER) ? process.env.VENDOO_DEPLOY_PROVIDER : 'none';
  const mode = MODES.has(process.env.COOLIFY_TRIGGER_MODE) ? process.env.COOLIFY_TRIGGER_MODE : 'webhook';
  const channel = CHANNELS.has(process.env.VENDOO_UPDATE_CHANNEL) ? process.env.VENDOO_UPDATE_CHANNEL : 'stable';
  const method = METHODS.has(process.env.COOLIFY_DEPLOY_METHOD) ? process.env.COOLIFY_DEPLOY_METHOD : 'GET';
  return {
    provider,
    coolify_url: String(process.env.COOLIFY_API_URL || '').replace(/\/$/, ''),
    resource_uuid: String(process.env.COOLIFY_RESOURCE_UUID || ''),
    trigger_mode: mode,
    deploy_method: method,
    deploy_endpoint: String(process.env.COOLIFY_DEPLOY_ENDPOINT || '/api/v1/deploy?uuid={uuid}&force={force}'),
    deployment_status_endpoint: String(process.env.COOLIFY_DEPLOYMENT_STATUS_ENDPOINT || '/api/v1/deployments/{deployment_id}'),
    github_repository: String(process.env.VENDOO_GITHUB_REPOSITORY || 'Masterluke77/vendoo'),
    github_branch: String(process.env.VENDOO_GITHUB_BRANCH || 'main'),
    ghcr_image: String(process.env.VENDOO_GHCR_IMAGE || 'ghcr.io/masterluke77/vendoo'),
    update_channel: channel,
    backup_before_deploy: !/^(0|false|no)$/i.test(String(process.env.VENDOO_BACKUP_BEFORE_DEPLOY || 'true')),
    backup_uploads: boolEnv('VENDOO_DEPLOY_BACKUP_UPLOADS', false),
    deployment_timeout_seconds: intEnv('VENDOO_DEPLOY_TIMEOUT_SECONDS', 900, 60, 7200),
    health_poll_seconds: intEnv('VENDOO_DEPLOY_HEALTH_POLL_SECONDS', 15, 5, 300),
    api_token: getSecretMetadata('COOLIFY_API_TOKEN'),
    deploy_webhook: getSecretMetadata('COOLIFY_DEPLOY_WEBHOOK_URL'),
  };
}

function configuredState(settings) {
  return settings.provider === 'coolify' && (
    (settings.trigger_mode === 'webhook' && settings.deploy_webhook.configured) ||
    (settings.trigger_mode === 'api' && settings.coolify_url && settings.resource_uuid && settings.api_token.configured)
  );
}

function publicSettings(repository = deploymentRepository) {
  const settings = currentSettings();
  return {
    ...settings,
    configured: configuredState(settings),
    api_token: { configured: settings.api_token.configured, masked: settings.api_token.masked, provider: settings.api_token.provider },
    deploy_webhook: { configured: settings.deploy_webhook.configured, masked: settings.deploy_webhook.masked, provider: settings.deploy_webhook.provider },
    current_version: APP_VERSION,
    build_revision: buildRevision(),
    active_run: repository.getActiveRun(),
    recent_runs: repository.listRuns(10).map(run => ({ ...run, events: repository.listEvents(run.id, 50) })),
    docker_socket_access: false,
  };
}

function buildApiUrl(settings, template, replacements) {
  let path = template;
  for (const [name, value] of Object.entries(replacements)) path = path.replaceAll(`{${name}}`, encodeURIComponent(String(value ?? '')));
  return `${settings.coolify_url}${path}`;
}

function buildApiDeployUrl(settings, force) {
  const endpoint = normalizeEndpoint(settings.deploy_endpoint, '/api/v1/deploy?uuid={uuid}&force={force}', ['uuid', 'force']);
  return buildApiUrl(settings, endpoint, { uuid: settings.resource_uuid, force: force ? 'true' : 'false' });
}

function buildApiStatusUrl(settings, deploymentId) {
  const endpoint = normalizeEndpoint(settings.deployment_status_endpoint, '/api/v1/deployments/{deployment_id}', ['deployment_id']);
  return buildApiUrl(settings, endpoint, { deployment_id: deploymentId });
}

async function fetchWithTimeout(fetchImpl, url, options = {}, timeoutMs = 15000) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
  try { return await fetchImpl(url, { ...options, signal: controller.signal, redirect: 'error' }); }
  catch (error) {
    if (error?.name === 'AbortError') fail('Anfrage hat das Zeitlimit überschritten.', 'REQUEST_TIMEOUT', 504);
    fail(`Ziel ist nicht erreichbar: ${error.message}`, 'REMOTE_UNREACHABLE', 502);
  } finally { clearTimeout(timeout); }
}

async function readSafeResponse(response) {
  const text = (await response.text()).slice(0, 64 * 1024);
  try { return text ? JSON.parse(text) : null; } catch { return text || null; }
}

function redact(value, depth = 0) {
  if (depth > 8) return '[depth-limit]';
  if (Array.isArray(value)) return value.slice(0, 100).map(item => redact(item, depth + 1));
  if (!value || typeof value !== 'object') return typeof value === 'string' ? value.slice(0, 4000) : value;
  const result = {};
  for (const [key, item] of Object.entries(value)) {
    if (/token|secret|password|authorization|webhook|credential|key/i.test(key)) result[key] = '[redacted]';
    else result[key] = redact(item, depth + 1);
  }
  return result;
}

function extractDeploymentId(value, depth = 0) {
  if (depth > 6 || value == null) return null;
  if (Array.isArray(value)) {
    for (const item of value) { const found = extractDeploymentId(item, depth + 1); if (found) return found; }
    return null;
  }
  if (typeof value !== 'object') return null;
  for (const key of ['deployment_id', 'deployment_uuid', 'deploymentUuid', 'uuid', 'id']) {
    const candidate = value[key];
    if (candidate && /^[a-zA-Z0-9_-]{4,160}$/.test(String(candidate))) return String(candidate);
  }
  for (const item of Object.values(value)) { const found = extractDeploymentId(item, depth + 1); if (found) return found; }
  return null;
}

function extractRemoteStatus(value) {
  if (!value || typeof value !== 'object') return '';
  const candidates = [value.status, value.state, value.deployment_status, value.data?.status, value.data?.state];
  return String(candidates.find(Boolean) || '').trim().toLowerCase();
}

function semver(value) {
  const match = String(value || '').trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
  if (!match) return null;
  return { raw: String(value).replace(/^v/, ''), major: +match[1], minor: +match[2], patch: +match[3], prerelease: match[4] || '' };
}

function compareVersions(a, b) {
  const left = semver(a); const right = semver(b);
  if (!left || !right) fail('Update-Manifest enthält eine ungültige SemVer-Version.', 'UPDATE_VERSION_INVALID', 502);
  for (const key of ['major', 'minor', 'patch']) if (left[key] !== right[key]) return left[key] > right[key] ? 1 : -1;
  if (left.prerelease === right.prerelease) return 0;
  if (!left.prerelease) return 1;
  if (!right.prerelease) return -1;
  return left.prerelease.localeCompare(right.prerelease, undefined, { numeric: true });
}

function validateUpdateResult(result, channel) {
  if (!result?.configured) return { ...result, current_version: APP_VERSION, channel };
  const latest = String(result.latest_version || result.version || result.manifest?.version || '');
  const parsed = semver(latest);
  if (!parsed) fail('Update-Manifest enthält keine gültige SemVer-Version.', 'UPDATE_VERSION_INVALID', 502);
  if (channel === 'stable' && parsed.prerelease) fail('Der Stable-Kanal akzeptiert keine Vorabversion.', 'UPDATE_CHANNEL_MISMATCH', 502);
  const manifest = result.manifest && typeof result.manifest === 'object' ? result.manifest : {};
  const manifestChannel = String(manifest.channel || channel).toLowerCase();
  if (!CHANNELS.has(manifestChannel) || (channel === 'stable' && manifestChannel !== 'stable')) {
    fail('Update-Manifest gehört nicht zum konfigurierten Update-Kanal.', 'UPDATE_CHANNEL_MISMATCH', 502);
  }
  const updateAvailable = compareVersions(latest, APP_VERSION) > 0;
  const sha256 = String(manifest.sha256 || manifest.package_sha256 || result.sha256 || '').trim().toLowerCase();
  if (updateAvailable && !/^[a-f0-9]{64}$/.test(sha256)) fail('Update-Manifest enthält keine gültige SHA-256-Prüfsumme.', 'UPDATE_CHECKSUM_MISSING', 502);
  const downloadUrl = String(manifest.download_url || manifest.package_url || result.download_url || '').trim();
  if (downloadUrl) {
    let url;
    try { url = new URL(downloadUrl); } catch { fail('Update-Manifest enthält eine ungültige Paket-URL.', 'UPDATE_URL_INVALID', 502); }
    if (url.protocol !== 'https:') fail('Update-Pakete müssen über HTTPS bereitgestellt werden.', 'UPDATE_URL_INSECURE', 502);
  }
  return {
    ...result,
    configured: true,
    current_version: APP_VERSION,
    latest_version: parsed.raw,
    update_available: updateAvailable,
    channel,
    manifest: { ...manifest, sha256, download_url: downloadUrl || '' },
  };
}

function directorySize(root, maxEntries = 100000) {
  let bytes = 0; let files = 0; let truncated = false;
  const queue = [root];
  while (queue.length) {
    const current = queue.pop();
    if (!existsSync(current)) continue;
    let entries;
    try { entries = readdirSync(current, { withFileTypes: true }); } catch { continue; }
    for (const entry of entries) {
      if (++files > maxEntries) { truncated = true; queue.length = 0; break; }
      const absolute = join(current, entry.name);
      try {
        if (entry.isDirectory()) queue.push(absolute);
        else if (entry.isFile()) bytes += statSync(absolute).size;
      } catch {}
    }
  }
  return { path: root, bytes, files, truncated };
}

function fluxStatus(getPlatformSnapshot) {
  try {
    const module = getPlatformSnapshot?.()?.modules?.find(item => item.id === 'vendoo.flux-studio');
    if (module) return { enabled: Boolean(module.enabled), state: module.state, source: 'platform-kernel' };
  } catch {}
  try {
    if (existsSync(MODULE_STATE_PATH)) {
      const state = JSON.parse(readFileSync(MODULE_STATE_PATH, 'utf8'));
      const record = state?.modules?.['vendoo.flux-studio'];
      if (record) return { enabled: record.enabled !== false, state: record.enabled === false ? 'disabled' : 'configured', source: 'module-state' };
    }
  } catch {}
  const initiallyDisabled = String(process.env.VENDOO_INITIAL_DISABLED_MODULES || '').split(',').map(item => item.trim()).includes('vendoo.flux-studio');
  return { enabled: !initiallyDisabled, state: initiallyDisabled ? 'disabled' : 'default-enabled', source: 'environment-default' };
}

function internalReadiness() {
  const checks = [];
  try {
    const value = db.prepare('SELECT 1 AS ok').get();
    checks.push({ name: 'database', ok: Number(value?.ok) === 1 });
  } catch (error) { checks.push({ name: 'database', ok: false, error: error.message }); }
  for (const item of verifyRuntimeWritable()) checks.push({ name: 'filesystem', ok: item.writable, path: item.path, error: item.error || null });
  return { ok: checks.every(item => item.ok), checks };
}

async function probePublicReadiness(fetchImpl, timeoutMs = 8000) {
  const base = String(process.env.PUBLIC_BASE_URL || `http://127.0.0.1:${process.env.PORT || 8124}`).replace(/\/$/, '');
  const response = await fetchWithTimeout(fetchImpl, `${base}/readyz`, { headers: { Accept: 'application/json', 'User-Agent': `Vendoo/${APP_VERSION}` } }, timeoutMs);
  const body = await readSafeResponse(response);
  return { ok: response.ok && body?.ok !== false, http_status: response.status, version: body?.version || null, body: redact(body) };
}

function productionChecks({ getPlatformSnapshot } = {}) {
  const results = [];
  const add = (id, status, title, message, details = {}) => results.push({ id, status, title, message, details });
  const publicBase = String(process.env.PUBLIC_BASE_URL || '').trim();
  let publicUrl = null;
  try { publicUrl = publicBase ? new URL(publicBase) : null; } catch {}
  add('https', publicUrl?.protocol === 'https:' ? 'pass' : 'fail', 'Öffentliche HTTPS-URL', publicUrl?.protocol === 'https:' ? publicUrl.origin : 'PUBLIC_BASE_URL fehlt oder verwendet nicht HTTPS.');
  const secureCookieMode = String(process.env.VENDOO_COOKIE_SECURE || 'auto').trim().toLowerCase();
  const secureCookies = secureCookieMode === 'true' || (secureCookieMode === 'auto' && publicUrl?.protocol === 'https:');
  add('secure-cookies', secureCookies ? 'pass' : 'fail', 'Sichere Cookies', secureCookies ? `Secure-Cookies sind aktiv (${secureCookieMode}).` : 'VENDOO_COOKIE_SECURE ist nicht aktiv oder Auto-Erkennung hat keine HTTPS-URL.');
  add('trust-proxy', boolEnv('VENDOO_TRUST_PROXY', false) ? 'pass' : 'warning', 'Trust Proxy', boolEnv('VENDOO_TRUST_PROXY', false) ? 'Reverse-Proxy-Vertrauen ist aktiviert.' : 'VENDOO_TRUST_PROXY ist nicht aktiv.');

  const expectedHost = publicUrl?.host || '';
  const hosts = String(process.env.VENDOO_ALLOWED_HOSTS || '').split(',').map(item => item.trim()).filter(Boolean);
  const origins = String(process.env.VENDOO_ALLOWED_ORIGINS || '').split(',').map(item => item.trim()).filter(Boolean);
  add('allowed-host', expectedHost && hosts.includes(expectedHost) ? 'pass' : 'fail', 'Allowed Host', expectedHost && hosts.includes(expectedHost) ? expectedHost : 'Produktivhost fehlt in VENDOO_ALLOWED_HOSTS.', { expected: expectedHost, configured_count: hosts.length });
  add('allowed-origin', publicUrl?.origin && origins.includes(publicUrl.origin) ? 'pass' : 'fail', 'Allowed Origin', publicUrl?.origin && origins.includes(publicUrl.origin) ? publicUrl.origin : 'Produktivorigin fehlt in VENDOO_ALLOWED_ORIGINS.', { expected: publicUrl?.origin || '', configured_count: origins.length });

  const persistent = DATA_ROOT !== APP_ROOT && dirname(DB_PATH).startsWith(DATA_ROOT) && UPLOADS_DIR.startsWith(DATA_ROOT) && BACKUP_DIR.startsWith(DATA_ROOT);
  add('persistent-paths', persistent ? 'pass' : 'fail', 'Persistente Pfade', persistent ? `Daten liegen unter ${DATA_ROOT}.` : 'Mindestens ein Laufzeitpfad liegt nicht im persistenten Datenbereich.', runtimePathSummary());
  const writable = verifyRuntimeWritable();
  add('writable', writable.every(item => item.writable) ? 'pass' : 'fail', 'Schreibrechte', writable.every(item => item.writable) ? 'Alle Laufzeitpfade sind beschreibbar.' : 'Mindestens ein Laufzeitpfad ist nicht beschreibbar.', { paths: writable });

  let integrity = 'unknown';
  try { integrity = String(db.pragma('integrity_check', { simple: true })); } catch (error) { integrity = error.message; }
  add('sqlite-integrity', integrity === 'ok' ? 'pass' : 'fail', 'SQLite-Integrität', integrity === 'ok' ? 'SQLite meldet ok.' : `SQLite-Integritätsprüfung: ${integrity}`);
  add('backup-target', existsSync(BACKUP_DIR) ? 'pass' : 'fail', 'Backupziel', existsSync(BACKUP_DIR) ? BACKUP_DIR : 'Backupziel fehlt.');

  const flux = fluxStatus(getPlatformSnapshot);
  add('flux-disabled', flux.enabled ? 'fail' : 'pass', 'FLUX Studio deaktiviert', flux.enabled ? 'FLUX Studio ist aktiv und widerspricht dem Produktions-Scope.' : 'FLUX Studio ist deaktiviert.', flux);
  const settings = currentSettings();
  add('deployment-secret', configuredState(settings) ? 'pass' : 'warning', 'Coolify-Zugang', configuredState(settings) ? 'Deployment-Zugang ist gesetzt; Secretwerte werden nicht angezeigt.' : 'Coolify-Deployment ist noch nicht vollständig konfiguriert.', {
    mode: settings.trigger_mode,
    api_token_configured: settings.api_token.configured,
    webhook_configured: settings.deploy_webhook.configured,
  });
  const autoDeploy = String(process.env.VENDOO_COOLIFY_AUTO_DEPLOY || '').trim().toLowerCase();
  add('auto-deploy', ['false', '0', 'off', 'no'].includes(autoDeploy) ? 'pass' : 'warning', 'Coolify Auto Deploy', ['false', '0', 'off', 'no'].includes(autoDeploy) ? 'Auto Deploy ist als deaktiviert dokumentiert.' : 'Vendoo kann den Coolify-Schalter nicht sicher auslesen. Auto Deploy manuell deaktiviert lassen.', { configured: autoDeploy || 'unknown' });
  return results;
}

export function createDeploymentService(adapters = {}) {
  const {
    createBackup,
    verifyStoredBackup,
    checkForUpdates,
    repository = deploymentRepository,
    fetchImpl = globalThis.fetch,
    getPlatformSnapshot,
  } = adapters;
  if (typeof createBackup !== 'function') fail('Backup-Adapter fehlt.', 'DEPLOYMENT_ADAPTER_MISSING', 500);
  if (typeof verifyStoredBackup !== 'function') fail('Backup-Verifikationsadapter fehlt.', 'DEPLOYMENT_ADAPTER_MISSING', 500);
  if (typeof fetchImpl !== 'function') fail('Fetch-Adapter fehlt.', 'DEPLOYMENT_ADAPTER_MISSING', 500);

  let monitorTimer = null;
  let monitoring = false;

  async function checkUpdates() {
    if (typeof checkForUpdates !== 'function') return { configured: false, current_version: APP_VERSION, message: 'Updateprüfung ist nicht verfügbar.' };
    return validateUpdateResult(await checkForUpdates({ channel: currentSettings().update_channel }), currentSettings().update_channel);
  }

  async function testConnection() {
    const settings = currentSettings();
    if (settings.provider !== 'coolify') fail('Coolify ist nicht als Deployment-Provider aktiviert.');
    if (!settings.coolify_url) fail('Coolify URL fehlt.');
    const headers = { Accept: 'application/json', 'User-Agent': `Vendoo/${APP_VERSION}` };
    if (settings.trigger_mode === 'api') {
      const token = getSecret('COOLIFY_API_TOKEN');
      if (!token) fail('Coolify API-Token fehlt.');
      headers.Authorization = `Bearer ${token}`;
    }
    const response = await fetchWithTimeout(fetchImpl, `${settings.coolify_url}/api/health`, { headers }, 10000);
    const body = await readSafeResponse(response);
    if (!response.ok) fail(`Coolify Healthcheck antwortet mit HTTP ${response.status}.`, 'COOLIFY_HEALTH_FAILED', 502, { response: redact(body) });
    return { ok: true, status: response.status, response: redact(body), url: settings.coolify_url };
  }

  async function requestCoolify(settings, force) {
    let url;
    const headers = { Accept: 'application/json', 'User-Agent': `Vendoo/${APP_VERSION}` };
    const method = settings.deploy_method;
    if (settings.trigger_mode === 'webhook') {
      url = normalizeWebhookUrl(getSecret('COOLIFY_DEPLOY_WEBHOOK_URL'));
      if (!url) fail('Coolify Deploy-Webhook ist nicht konfiguriert.');
    } else {
      const token = getSecret('COOLIFY_API_TOKEN');
      if (!settings.coolify_url || !settings.resource_uuid || !token) fail('Coolify API URL, Resource UUID oder API Token fehlt.');
      url = buildApiDeployUrl(settings, Boolean(force));
      headers.Authorization = `Bearer ${token}`;
    }
    const response = await fetchWithTimeout(fetchImpl, url, { method, headers }, 20000);
    const body = await readSafeResponse(response);
    if (!response.ok) fail(`Coolify hat den Deployment-Auftrag mit HTTP ${response.status} abgewiesen.`, 'COOLIFY_DEPLOY_REJECTED', 502, { response: redact(body) });
    return { method, http_status: response.status, body: redact(body), deployment_id: extractDeploymentId(body) };
  }

  async function trigger(input = {}, context = {}) {
    if (input.confirmation !== 'DEPLOY') fail('Zur Bestätigung muss exakt DEPLOY übermittelt werden.', 'DEPLOYMENT_CONFIRMATION_REQUIRED');
    const settings = currentSettings();
    if (!configuredState(settings)) fail('Coolify ist nicht vollständig konfiguriert.', 'DEPLOYMENT_NOT_CONFIGURED');
    const targetVersion = clean(input.target_version || APP_VERSION, 40).replace(/^v/, '');
    if (!semver(targetVersion)) fail('Zielversion ist keine gültige SemVer-Version.', 'DEPLOYMENT_TARGET_VERSION_INVALID');
    const idempotencyKey = cleanIdempotencyKey(input.idempotency_key || context.idempotencyKey);
    const existing = repository.findByIdempotencyKey(idempotencyKey);
    if (existing) return { ...existing, reused: true, events: repository.listEvents(existing.id) };

    if (compareVersions(targetVersion, APP_VERSION) > 0) {
      const update = await checkUpdates();
      if (!update.configured || update.latest_version !== targetVersion || !update.update_available) {
        fail('Zielversion ist nicht durch das freigegebene Update-Manifest bestätigt.', 'DEPLOYMENT_TARGET_NOT_APPROVED', 409, { target_version: targetVersion, update });
      }
    }

    const timeoutAt = new Date(Date.now() + settings.deployment_timeout_seconds * 1000).toISOString();
    const created = repository.createRun({
      idempotencyKey,
      provider: settings.provider,
      triggerMode: settings.trigger_mode,
      currentVersion: APP_VERSION,
      targetVersion,
      buildRevisionBefore: buildRevision(),
      requestedBy: context.userId || null,
      reason: clean(input.reason || 'manual', 120),
      githubRepository: settings.github_repository,
      githubBranch: settings.github_branch,
      timeoutAt,
      request: { force: Boolean(input.force), target_version: targetVersion, backup_required: settings.backup_before_deploy },
    });
    if (created.reused) return { ...created.run, reused: true, events: repository.listEvents(created.run.id) };
    let run = created.run;

    try {
      if (settings.backup_before_deploy) {
        run = repository.transition(run.id, 'backup_pending', { message: 'Vorab-Backup ist verpflichtend.' });
        run = repository.transition(run.id, 'backup_running', { message: 'Vorab-Backup wird erstellt.' });
        const backup = await createBackup({
          kind: 'manual', includeUploads: settings.backup_uploads, includeConfig: true,
          label: `pre-deploy-${APP_VERSION}-to-${targetVersion}`,
        });
        const verification = verifyStoredBackup(backup.id);
        repository.recordBackupVerification({
          backupId: backup.id,
          runId: run.id,
          status: verification.ok ? 'verified' : 'invalid',
          sha256: backup.sha256,
          fileSize: backup.file_size,
          sqliteIntegrity: verification.ok ? 'ok' : 'failed',
          manifest: verification.manifest || backup.manifest || {},
          errors: verification.errors || [],
          warnings: verification.warnings || [],
        });
        if (!verification.ok) fail(`Vorab-Backup ist nicht verifiziert: ${(verification.errors || []).join('; ')}`, 'DEPLOYMENT_BACKUP_INVALID', 409);
        run = repository.transition(run.id, 'backup_verified', {
          message: 'Vorab-Backup wurde erstellt und verifiziert.',
          patch: { backup_id: backup.id },
          details: { backup_id: backup.id, sha256: backup.sha256, file_size: backup.file_size },
        });
      }

      run = repository.transition(run.id, 'deploy_requested', { message: 'Coolify-Deployment wird angefordert.' });
      const remote = await requestCoolify(settings, Boolean(input.force));
      run = repository.transition(run.id, settings.trigger_mode === 'api' ? 'deploy_running' : 'health_wait', {
        message: settings.trigger_mode === 'api' ? 'Coolify hat den Deployment-Auftrag angenommen.' : 'Webhook wurde angenommen; externe Bestätigung oder neue Zielversion wird abgewartet.',
        eventType: 'coolify.accepted',
        patch: { deployment_id: remote.deployment_id || null, result_json: remote },
        details: { http_status: remote.http_status, deployment_id: remote.deployment_id || null },
      });
      scheduleMonitor(1000);
      return { ...run, reused: false, events: repository.listEvents(run.id), message: 'Deployment-Auftrag angenommen. Erfolg wird erst nach Status- und Readinessprüfung gemeldet.' };
    } catch (error) {
      const latest = repository.getRun(run.id);
      if (latest && !['failed', 'succeeded'].includes(latest.state)) {
        try {
          repository.transition(run.id, 'failed', {
            message: error.message || 'Deployment-Auftrag ist fehlgeschlagen.',
            eventType: 'run.failed',
            patch: { error_code: error.code || 'DEPLOYMENT_FAILED', error_message: String(error.message || error).slice(0, 2000) },
          });
        } catch {}
      }
      throw error;
    }
  }

  async function pollCoolifyStatus(run, settings) {
    if (settings.trigger_mode !== 'api' || !run.deployment_id || !settings.coolify_url) return { available: false };
    const token = getSecret('COOLIFY_API_TOKEN');
    if (!token) return { available: false, error: 'API-Token fehlt.' };
    const url = buildApiStatusUrl(settings, run.deployment_id);
    const response = await fetchWithTimeout(fetchImpl, url, {
      headers: { Accept: 'application/json', Authorization: `Bearer ${token}`, 'User-Agent': `Vendoo/${APP_VERSION}` },
    }, 10000);
    const body = await readSafeResponse(response);
    if (!response.ok) return { available: true, ok: false, http_status: response.status, body: redact(body) };
    const status = extractRemoteStatus(body);
    return { available: true, ok: true, http_status: response.status, status, body: redact(body) };
  }

  async function refreshRun(runId = null, { externalConfirmed = false } = {}) {
    const run = runId ? repository.getRun(runId) : repository.getActiveRun();
    if (!run) return { state: 'idle', message: 'Kein aktives Deployment.' };
    if (['succeeded', 'failed', 'rollback_required'].includes(run.state)) return { ...run, events: repository.listEvents(run.id) };
    if (run.timeout_at && Date.parse(run.timeout_at) <= Date.now()) {
      const timedOut = repository.transition(run.id, 'rollback_required', {
        message: 'Deployment-Zeitlimit überschritten. Rollback oder manuelle Prüfung erforderlich.',
        eventType: 'run.timeout',
        patch: { error_code: 'DEPLOYMENT_TIMEOUT', error_message: 'Zeitlimit überschritten.' },
      });
      return { ...timedOut, events: repository.listEvents(run.id) };
    }

    const settings = currentSettings();
    let remote = { available: false };
    try { remote = await pollCoolifyStatus(run, settings); }
    catch (error) {
      repository.patchRun(run.id, {}, { eventType: 'coolify.status_error', message: 'Coolify-Status konnte nicht gelesen werden.', details: { error: error.message } });
    }
    if (remote.status && TERMINAL_COOLIFY_FAILURE.has(remote.status)) {
      const failed = repository.transition(run.id, 'rollback_required', {
        message: `Coolify meldet den Fehlerstatus ${remote.status}.`,
        eventType: 'coolify.failed',
        patch: { result_json: remote, error_code: 'COOLIFY_DEPLOYMENT_FAILED', error_message: `Coolify-Status: ${remote.status}` },
      });
      return { ...failed, remote, events: repository.listEvents(run.id) };
    }
    if (run.state === 'deploy_running' && remote.available && remote.ok) {
      repository.transition(run.id, 'health_wait', { message: 'Deploymentstatus wurde gelesen; Readiness und Zielversion werden geprüft.', eventType: 'health.wait', details: { remote_status: remote.status || 'unknown' } });
    }

    let readiness;
    try { readiness = await probePublicReadiness(fetchImpl, 8000); }
    catch (error) { readiness = { ok: false, error: error.message }; }
    const targetMatches = readiness.ok && String(readiness.version || '').replace(/^v/, '') === String(run.target_version).replace(/^v/, '');
    const remoteSucceeded = remote.available && remote.ok && TERMINAL_COOLIFY_SUCCESS.has(remote.status);
    const persistedConfirmation = repository.listEvents(run.id).some(event => event.event_type === 'external.succeeded');
    const confirmed = Boolean(externalConfirmed || persistedConfirmation);
    const deploymentConfirmed = settings.trigger_mode === 'api' ? (remoteSucceeded || confirmed) : confirmed;
    if (readiness.ok && targetMatches && deploymentConfirmed) {
      const succeeded = repository.transition(run.id, 'succeeded', {
        message: 'Deployment, Readiness und Zielversion wurden erfolgreich bestätigt.',
        eventType: 'run.succeeded',
        patch: { build_revision_after: buildRevision(), result_json: { remote, readiness } },
        details: { target_version: run.target_version, readiness_version: readiness.version, remote_status: remote.status || null },
      });
      return { ...succeeded, remote, readiness, events: repository.listEvents(run.id) };
    }
    repository.patchRun(run.id, { result_json: { remote, readiness } }, {
      eventType: 'health.pending',
      message: readiness.ok ? 'Readiness ist grün, aber Zielversion oder externe Bestätigung fehlt.' : 'Readiness ist noch nicht grün.',
      details: { readiness_ok: Boolean(readiness.ok), readiness_version: readiness.version || null, target_version: run.target_version, remote_status: remote.status || null, external_confirmation: confirmed, deployment_confirmed: deploymentConfirmed },
    });
    return { ...repository.getRun(run.id), remote, readiness, events: repository.listEvents(run.id) };
  }

  async function confirmExternal(runId, input = {}) {
    if (input.confirmation !== 'CONFIRM') fail('Zur externen Bestätigung muss exakt CONFIRM übermittelt werden.', 'DEPLOYMENT_EXTERNAL_CONFIRMATION_REQUIRED');
    const run = repository.getRun(runId);
    if (!run) fail('Deployment-Auftrag wurde nicht gefunden.', 'DEPLOYMENT_RUN_NOT_FOUND', 404);
    if (!['deploy_requested', 'deploy_running', 'health_wait', 'rollback_required'].includes(run.state)) fail('Dieser Deployment-Auftrag kann nicht extern bestätigt werden.', 'DEPLOYMENT_CONFIRMATION_STATE_INVALID', 409);
    const outcome = clean(input.outcome || 'succeeded', 20);
    if (outcome === 'failed') {
      const nextState = run.state === 'rollback_required' ? 'failed' : 'rollback_required';
      return repository.transition(run.id, nextState, {
        message: clean(input.message || 'Externes Deployment wurde als fehlgeschlagen bestätigt.', 500),
        eventType: 'external.failed',
        patch: { error_code: 'EXTERNAL_DEPLOYMENT_FAILED', error_message: clean(input.message || 'Externe Fehlermeldung.', 1000) },
      });
    }
    if (outcome === 'running') {
      if (run.state === 'deploy_requested') repository.transition(run.id, 'deploy_running', { message: 'Externes System bestätigt laufendes Deployment.', eventType: 'external.running' });
      return refreshRun(run.id);
    }
    if (outcome !== 'succeeded') fail('Externe Bestätigung muss running, succeeded oder failed verwenden.');
    repository.addEvent(run.id, run.state, 'external.succeeded', clean(input.message || 'Externes System meldet Abschluss.', 500), {});
    return refreshRun(run.id, { externalConfirmed: true });
  }

  function scheduleMonitor(delayMs = null) {
    if (monitorTimer) clearTimeout(monitorTimer);
    const interval = currentSettings().health_poll_seconds * 1000;
    monitorTimer = setTimeout(async () => {
      if (monitoring) return scheduleMonitor(interval);
      monitoring = true;
      try { await refreshRun(); } catch (error) { console.error('Deployment-Monitor:', error.message); }
      finally {
        monitoring = false;
        const active = repository.getActiveRun();
        if (active && active.state !== 'rollback_required') scheduleMonitor(interval);
      }
    }, delayMs ?? interval);
    monitorTimer.unref?.();
  }

  async function status() {
    const settings = publicSettings(repository);
    const lastBackup = db.prepare(`SELECT id, kind, label, file_name, file_size, sha256, status, verified_at, created_at
      FROM operation_backups WHERE status = 'verified' ORDER BY created_at DESC LIMIT 1`).get() || null;
    return {
      ...settings,
      production: {
        version: APP_VERSION,
        build_revision: buildRevision(),
        deployment_provider: settings.provider,
        domain: (() => { try { return new URL(String(process.env.PUBLIC_BASE_URL || '')).host; } catch { return ''; } })(),
        public_base_url: String(process.env.PUBLIC_BASE_URL || ''),
        readiness: internalReadiness(),
        database: { path: DB_PATH, integrity: (() => { try { return db.pragma('integrity_check', { simple: true }); } catch (error) { return error.message; } })() },
        storage: {
          data: directorySize(DATA_ROOT), uploads: directorySize(UPLOADS_DIR), backups: directorySize(BACKUP_DIR),
          operations: directorySize(OPERATIONS_DIR), logs: directorySize(LOG_DIR), modules: directorySize(MODULES_DIR),
        },
        last_backup: lastBackup,
        last_deployment: repository.listRuns(1)[0] || null,
        flux: fluxStatus(getPlatformSnapshot),
      },
      latest_production_check: repository.latestProductionCheck(),
    };
  }

  function update(input = {}) {
    const provider = clean(input.provider || 'none', 20);
    const mode = clean(input.trigger_mode || 'webhook', 20);
    const channel = clean(input.update_channel || 'stable', 20);
    const method = clean(input.deploy_method || 'GET', 10).toUpperCase();
    if (!PROVIDERS.has(provider)) fail('Unbekannter Deployment-Provider.');
    if (!MODES.has(mode)) fail('Unbekannter Coolify Trigger-Modus.');
    if (!CHANNELS.has(channel)) fail('Unbekannter Update-Kanal.');
    if (!METHODS.has(method)) fail('Coolify Deploy-Methode muss GET oder POST sein.');

    const values = {
      VENDOO_DEPLOY_PROVIDER: provider,
      COOLIFY_API_URL: normalizeBaseUrl(input.coolify_url || ''),
      COOLIFY_RESOURCE_UUID: cleanUuid(input.resource_uuid || ''),
      COOLIFY_TRIGGER_MODE: mode,
      COOLIFY_DEPLOY_METHOD: method,
      COOLIFY_DEPLOY_ENDPOINT: normalizeEndpoint(input.deploy_endpoint, '/api/v1/deploy?uuid={uuid}&force={force}', ['uuid', 'force']),
      COOLIFY_DEPLOYMENT_STATUS_ENDPOINT: normalizeEndpoint(input.deployment_status_endpoint, '/api/v1/deployments/{deployment_id}', ['deployment_id']),
      VENDOO_GITHUB_REPOSITORY: clean(input.github_repository || 'Masterluke77/vendoo', 200),
      VENDOO_GITHUB_BRANCH: clean(input.github_branch || 'main', 120),
      VENDOO_GHCR_IMAGE: clean(input.ghcr_image || 'ghcr.io/masterluke77/vendoo', 300),
      VENDOO_UPDATE_CHANNEL: channel,
      VENDOO_BACKUP_BEFORE_DEPLOY: input.backup_before_deploy === false ? 'false' : 'true',
      VENDOO_DEPLOY_BACKUP_UPLOADS: input.backup_uploads === true ? 'true' : 'false',
      VENDOO_DEPLOY_TIMEOUT_SECONDS: String(Math.max(60, Math.min(7200, Number(input.deployment_timeout_seconds) || 900))),
      VENDOO_DEPLOY_HEALTH_POLL_SECONDS: String(Math.max(5, Math.min(300, Number(input.health_poll_seconds) || 15))),
    };
    let content = readRuntimeConfig();
    for (const [key, value] of Object.entries(values)) {
      content = setConfigValue(content, key, value);
      process.env[key] = value;
    }
    writeRuntimeConfig(content);
    if (input.api_token !== undefined && input.api_token !== '' && input.api_token !== MASKED_SECRET) {
      setSecret('COOLIFY_API_TOKEN', clean(input.api_token, 16384), { source: 'deployment-ui' });
    }
    if (input.deploy_webhook_url !== undefined && input.deploy_webhook_url !== '' && input.deploy_webhook_url !== MASKED_SECRET) {
      setSecret('COOLIFY_DEPLOY_WEBHOOK_URL', normalizeWebhookUrl(input.deploy_webhook_url), { source: 'deployment-ui' });
    }
    return publicSettings(repository);
  }

  function runProductionCheck(context = {}) {
    const results = productionChecks({ getPlatformSnapshot });
    return repository.createProductionCheck({ createdBy: context.userId || null, results });
  }

  scheduleMonitor(2500);

  return Object.freeze({
    status,
    update,
    testConnection,
    trigger,
    checkUpdates,
    refreshRun,
    confirmExternal,
    listRuns(limit) { return repository.listRuns(limit).map(run => ({ ...run, events: repository.listEvents(run.id) })); },
    getRun(id) { const run = repository.getRun(id); return run ? { ...run, events: repository.listEvents(id) } : null; },
    runProductionCheck,
    latestProductionCheck: repository.latestProductionCheck,
    health() {
      const settings = publicSettings(repository);
      return { ok: true, provider: settings.provider, configured: settings.configured, state: settings.active_run?.state || 'idle', docker_socket_access: false };
    },
    stop() { if (monitorTimer) clearTimeout(monitorTimer); monitorTimer = null; },
  });
}
