/**
 * Startup Validation Module
 * Extracts startup configuration validation and health checker sync from server.js
 * (Phase 3 refactoring)
 */

const fs = require('fs');
const fsp = require('fs').promises;
const path = require('path');
const http = require('http');
const https = require('https');
const { exists, isAccessible } = require('./fs-helpers');
const { resolveServiceUrl } = require('./url-resolver');

/**
 * Validate startup configuration and environment.
 * Fail fast with clear error messages if critical issues are found.
 *
 * @param {Object} deps
 * @param {Function} deps.log - structured logger
 * @param {string} deps.CADDYFILE_PATH
 * @param {string} deps.SERVICES_FILE
 * @param {string} deps.CONFIG_FILE
 * @param {string} deps.CADDY_ADMIN_URL
 * @param {number} deps.PORT
 */
async function validateStartupConfig({ log, CADDYFILE_PATH, SERVICES_FILE, CONFIG_FILE, CADDY_ADMIN_URL, PORT }) {
  const errors = [];
  const warnings = [];

  log.info('startup', 'Validating startup configuration...');

  // 1. Check if Caddyfile exists and is writable
  try {
    if (await exists(CADDYFILE_PATH)) {
      if (!(await isAccessible(CADDYFILE_PATH, fs.constants.R_OK | fs.constants.W_OK))) {
        errors.push(`Caddyfile is not readable/writable: ${CADDYFILE_PATH}`);
      } else {
        log.info('startup', 'Caddyfile is accessible', { path: CADDYFILE_PATH });
      }
    } else {
      warnings.push(`Caddyfile does not exist: ${CADDYFILE_PATH} (will be created if needed)`);
    }
  } catch (error) {
    errors.push(`Caddyfile is not readable/writable: ${CADDYFILE_PATH}`);
  }

  // 2. Check if services file exists or can be created
  const servicesDir = path.dirname(SERVICES_FILE);
  try {
    if (await exists(SERVICES_FILE)) {
      // Validate it's valid JSON
      try {
        const content = await fsp.readFile(SERVICES_FILE, 'utf8');
        JSON.parse(content);
        log.info('startup', 'Services file is valid JSON', { path: SERVICES_FILE });
      } catch (parseError) {
        errors.push(`Services file exists but contains invalid JSON: ${SERVICES_FILE}`);
      }
    } else {
      // Check if parent directory exists and is writable
      if (await exists(servicesDir)) {
        if (!(await isAccessible(servicesDir, fs.constants.W_OK))) {
          errors.push(`Cannot access services file or directory: ${SERVICES_FILE}`);
        } else {
          log.info('startup', 'Services file directory is writable', { path: servicesDir });
        }
      } else {
        errors.push(`Services file directory does not exist: ${servicesDir}`);
      }
    }
  } catch (error) {
    errors.push(`Cannot access services file or directory: ${SERVICES_FILE}`);
  }

  // 3. Check if port is available
  const net = require('net');
  const portCheckServer = net.createServer();
  try {
    portCheckServer.listen(PORT, '0.0.0.0');
    portCheckServer.close();
    log.info('startup', `Port ${PORT} is available`);
  } catch (error) {
    errors.push(`Port ${PORT} is already in use or cannot be bound`);
  }

  // 4. Check if config file is valid JSON (if exists)
  try {
    if (await exists(CONFIG_FILE)) {
      const content = await fsp.readFile(CONFIG_FILE, 'utf8');
      JSON.parse(content);
      log.info('startup', 'Config file is valid JSON', { path: CONFIG_FILE });
    } else {
      log.warn('startup', 'Config file does not exist (will use defaults)', { path: CONFIG_FILE });
    }
  } catch (error) {
    errors.push(`Config file exists but contains invalid JSON: ${CONFIG_FILE}`);
  }

  // 5. Check Caddy admin API reachability (warning only, not critical)
  const checkCaddyAdmin = () => {
    return new Promise((resolve) => {
      const urlObj = new URL(CADDY_ADMIN_URL);
      const client = urlObj.protocol === 'https:' ? https : http;

      const req = client.request({
        hostname: urlObj.hostname,
        port: urlObj.port,
        path: '/config/',
        method: 'GET',
        timeout: 2000
      }, (res) => {
        resolve(res.statusCode >= 200 && res.statusCode < 500);
      });

      req.on('error', () => resolve(false));
      req.on('timeout', () => {
        req.destroy();
        resolve(false);
      });
      req.end();
    });
  };

  // Run async Caddy check (don't block startup)
  checkCaddyAdmin().then(isReachable => {
    if (isReachable) {
      log.info('startup', 'Caddy admin API is reachable', { url: CADDY_ADMIN_URL });
    } else {
      log.warn('startup', 'Caddy admin API is not reachable (may start later)', { url: CADDY_ADMIN_URL });
    }
  });

  // Print warnings
  if (warnings.length > 0) {
    warnings.forEach(warning => log.warn('startup', warning));
  }

  // Fail fast if there are critical errors
  if (errors.length > 0) {
    errors.forEach(err => log.error('startup', err));
    log.error('startup', 'Cannot start server — fix the above errors and try again');
    process.exit(1);
  }

  log.info('startup', 'Startup configuration validation passed');
}

/**
 * Full-sync health checker from services.json + top-card services.
 * Adds missing, updates changed URLs, removes deleted services.
 *
 * @param {Object} deps
 * @param {Function} deps.log - structured logger
 * @param {string} deps.SERVICES_FILE
 * @param {Object} deps.servicesStateManager - StateManager instance
 * @param {Object} deps.healthChecker - health checker module
 * @param {Function} deps.buildServiceUrl - canonical URL builder
 * @param {Object} deps.siteConfig - site config (dnsServers, etc.)
 * @param {Object} deps.APP - app constants (USER_AGENTS)
 */
async function syncHealthCheckerServices({ log, SERVICES_FILE, servicesStateManager, healthChecker, buildServiceUrl, siteConfig, APP }) {
  try {
    const topCardServices = [
      { id: 'internet', name: 'Internet' },
    ];

    // Dynamically add all configured DNS servers from config
    for (const [id, info] of Object.entries(siteConfig?.dnsServers || {})) {
      topCardServices.push({ id, name: info.name || id.toUpperCase() });
    }

    let appServices = [];
    if (await exists(SERVICES_FILE)) {
      const data = await servicesStateManager.read();
      appServices = Array.isArray(data) ? data : data.services || [];
    }

    const allServices = [...topCardServices, ...appServices];
    const desiredIds = new Set();
    let added = 0, updated = 0, removed = 0;

    for (const svc of allServices) {
      const id = svc.id || svc.name?.toLowerCase();
      if (!id) continue;
      desiredIds.add(id);

      const url = resolveServiceUrl(id, svc, siteConfig, buildServiceUrl);
      const existing = healthChecker.config.services?.[id];

      if (!existing) {
        healthChecker.configureService(id, {
          name: svc.name || id,
          url,
          method: 'HEAD',
          timeout: 5000,
          expectedStatusCodes: [200, 201, 204, 301, 302, 303, 307, 308, 401, 403],
          headers: { 'User-Agent': APP.USER_AGENTS.HEALTH },
        });
        added++;
      } else if (existing.url !== url) {
        healthChecker.configureService(id, { ...existing, url });
        updated++;
      }
    }

    // Remove services no longer in the desired set
    const configuredIds = Object.keys(healthChecker.config.services || {});
    for (const id of configuredIds) {
      if (!desiredIds.has(id)) {
        healthChecker.removeService(id);
        removed++;
      }
    }

    if (added > 0 || updated > 0 || removed > 0) {
      log.info('health', 'Health checker synced', { added, updated, removed });
    }
  } catch (error) {
    log.error('health', 'Error syncing health checker', { error: error.message });
  }
}

module.exports = { validateStartupConfig, syncHealthCheckerServices };
