import { mkdirSync, existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { readRuntimeConfig, setConfigValue, writeRuntimeConfig } from '../../../lib/config-store.mjs';
import { getSecret, getSecretMetadata, hasSecret, setSecret } from '../../../lib/secret-store.mjs';
import { OPERATIONS_DIR, APP_VERSION } from '../../../lib/runtime-paths.mjs';
import { PlatformError } from '../../kernel/errors.mjs';

const LAST_DEPLOYMENT_PATH = join(OPERATIONS_DIR, 'last-deployment-request.json');
const PROVIDERS = new Set(['none', 'coolify']);
const MODES = new Set(['webhook', 'api']);
const CHANNELS = new Set(['stable', 'beta']);
const METHODS = new Set(['GET', 'POST']);

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 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 readLastDeployment() {
  try { return existsSync(LAST_DEPLOYMENT_PATH) ? JSON.parse(readFileSync(LAST_DEPLOYMENT_PATH, 'utf8')) : null; }
  catch { return null; }
}

function writeLastDeployment(value) {
  mkdirSync(dirname(LAST_DEPLOYMENT_PATH), { recursive: true });
  const temporary = `${LAST_DEPLOYMENT_PATH}.${process.pid}.tmp`;
  writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
  renameSync(temporary, LAST_DEPLOYMENT_PATH);
}

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}'),
    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: /^(1|true|yes)$/i.test(String(process.env.VENDOO_DEPLOY_BACKUP_UPLOADS || 'false')),
    api_token: getSecretMetadata('COOLIFY_API_TOKEN'),
    deploy_webhook: getSecretMetadata('COOLIFY_DEPLOY_WEBHOOK_URL'),
  };
}

function publicSettings() {
  const settings = currentSettings();
  const configured = 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)
  );
  return {
    ...settings,
    configured,
    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,
    last_deployment: readLastDeployment(),
    docker_socket_access: false,
  };
}

function buildApiDeployUrl(settings, force) {
  const endpoint = settings.deploy_endpoint || '/api/v1/deploy?uuid={uuid}&force={force}';
  if (!endpoint.startsWith('/')) fail('Coolify API-Endpunkt muss mit / beginnen.');
  if (/\r|\n|\0/.test(endpoint) || endpoint.includes('..')) fail('Coolify API-Endpunkt ist ungültig.');
  const path = endpoint
    .replaceAll('{uuid}', encodeURIComponent(settings.resource_uuid))
    .replaceAll('{force}', force ? 'true' : 'false');
  return `${settings.coolify_url}${path}`;
}

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, redirect: 'error' }); }
  catch (error) {
    if (error?.name === 'AbortError') fail('Coolify-Anfrage hat das Zeitlimit überschritten.', 'COOLIFY_TIMEOUT', 504);
    fail(`Coolify ist nicht erreichbar: ${error.message}`, 'COOLIFY_UNREACHABLE', 502);
  } finally { clearTimeout(timeout); }
}

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

export function createDeploymentService({ createBackup, checkForUpdates } = {}) {
  if (typeof createBackup !== 'function') fail('Backup-Adapter fehlt.', 'DEPLOYMENT_ADAPTER_MISSING', 500);

  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 response = await fetchWithTimeout(`${settings.coolify_url}/api/health`, { headers: { Accept: 'application/json' } }, 10000);
    const body = await readSafeResponse(response);
    if (!response.ok) fail(`Coolify Healthcheck antwortet mit HTTP ${response.status}.`, 'COOLIFY_HEALTH_FAILED', 502, { response: body });
    return { ok: true, status: response.status, response: body, url: settings.coolify_url };
  }

  async function trigger({ force = false, confirmation = '', reason = 'manual' } = {}) {
    if (confirmation !== 'DEPLOY') fail('Zur Bestätigung muss exakt DEPLOY übermittelt werden.', 'DEPLOYMENT_CONFIRMATION_REQUIRED');
    const settings = currentSettings();
    if (settings.provider !== 'coolify') fail('Coolify ist nicht als Deployment-Provider aktiviert.');

    let backup = null;
    if (settings.backup_before_deploy) {
      backup = await createBackup({
        kind: 'manual', includeUploads: settings.backup_uploads, includeConfig: true,
        label: `pre-deploy-${APP_VERSION}`,
      });
    }

    let url;
    const headers = { Accept: 'application/json', 'User-Agent': `Vendoo/${APP_VERSION}` };
    let 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 requestedAt = new Date().toISOString();
    const response = await fetchWithTimeout(url, { method, headers }, 20000);
    const body = await readSafeResponse(response);
    const record = {
      requested_at: requestedAt,
      provider: 'coolify', trigger_mode: settings.trigger_mode, method,
      resource_uuid: settings.resource_uuid || null,
      github_repository: settings.github_repository, github_branch: settings.github_branch,
      version: APP_VERSION, force: Boolean(force), reason: clean(reason, 120),
      accepted: response.ok, http_status: response.status,
      backup_id: backup?.id || null, backup_file: backup?.filename || null,
      response: body,
    };
    writeLastDeployment(record);
    if (!response.ok) fail(`Coolify hat den Deployment-Auftrag mit HTTP ${response.status} abgewiesen.`, 'COOLIFY_DEPLOY_REJECTED', 502, { response: body, backup_id: backup?.id || null });
    return { ...record, message: 'Coolify-Deployment wurde angefordert. Der laufende Container wird nicht selbst verändert.' };
  }

  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: clean(input.deploy_endpoint || '/api/v1/deploy?uuid={uuid}&force={force}', 500),
      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',
    };
    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 !== '••••••••') {
      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 !== '••••••••') {
      setSecret('COOLIFY_DEPLOY_WEBHOOK_URL', normalizeWebhookUrl(input.deploy_webhook_url), { source: 'deployment-ui' });
    }
    return publicSettings();
  }

  return Object.freeze({
    status: publicSettings,
    update,
    testConnection,
    trigger,
    async checkUpdates() {
      if (typeof checkForUpdates !== 'function') return { configured: false, current_version: APP_VERSION, message: 'Updateprüfung ist nicht verfügbar.' };
      return checkForUpdates();
    },
    health() {
      const status = publicSettings();
      return { ok: true, provider: status.provider, configured: status.configured, docker_socket_access: false };
    },
  });
}
