const express = require('express');
const fs = require('fs');
const http = require('http');
const https = require('https');
const tls = require('tls');
const validatorLib = require('validator');
const { APP, REGEX, TIMEOUTS } = require('../constants');
const { validateServiceConfig, isValidPort } = require('../input-validator');
const { exists } = require('../fs-helpers');
const { paginate, parsePaginationParams } = require('../pagination');
const { resolveServiceUrl } = require('../url-resolver');

module.exports = function(ctx) {
  const router = express.Router();
  const CA_CERT_PATH = process.env.CA_CERT_PATH || '/app/pki/root.crt';
  const PROBE_CONCURRENCY = 6;
  let probeHttpsAgent;

  try {
    const caCert = fs.readFileSync(CA_CERT_PATH);
    probeHttpsAgent = new https.Agent({ ca: [...tls.rootCertificates, caCert] });
  } catch (_) {
    probeHttpsAgent = new https.Agent();
  }

  function isServiceUp(statusCode) {
    return (statusCode >= 200 && statusCode < 400) || statusCode === 401 || statusCode === 403;
  }

  async function loadServicesList() {
    if (!await exists(ctx.SERVICES_FILE)) return [];
    const data = await ctx.servicesStateManager.read();
    return Array.isArray(data) ? data : data.services || [];
  }

  function resolveProbeUrl(id, service) {
    return resolveServiceUrl(id, service, ctx.siteConfig, ctx.buildServiceUrl);
  }

  function requestStatusCode(url, method) {
    const parsed = new URL(url);
    const isHttps = parsed.protocol === 'https:';
    const lib = isHttps ? https : http;

    return new Promise((resolve, reject) => {
      const req = lib.request({
        hostname: parsed.hostname,
        port: parsed.port || (isHttps ? 443 : 80),
        path: parsed.pathname + parsed.search,
        method,
        timeout: TIMEOUTS.HTTP_DEFAULT,
        agent: isHttps ? probeHttpsAgent : undefined,
        headers: { 'User-Agent': APP.USER_AGENTS.PROBE },
      }, (response) => {
        response.resume();
        resolve(response.statusCode || 0);
      });

      req.on('error', reject);
      req.on('timeout', () => {
        req.destroy();
        reject(new Error('Timeout'));
      });
      req.end();
    });
  }

  async function probeServiceStatus(id, service) {
    const startedAt = process.hrtime.bigint();
    let url = resolveProbeUrl(id, service);
    let statusCode = 502;
    let error = null;

    try {
      statusCode = await requestStatusCode(url, 'HEAD');
      if (statusCode === 405 || statusCode === 501) {
        statusCode = await requestStatusCode(url, 'GET');
      }
    } catch (primaryError) {
      error = primaryError;
      if (id !== 'internet') {
        const fallbackUrl = ctx.buildServiceUrl(id);
        if (fallbackUrl !== url) {
          try {
            statusCode = await requestStatusCode(fallbackUrl, 'GET');
            url = fallbackUrl;
            error = null;
          } catch (fallbackError) {
            error = fallbackError;
          }
        }
      }
    }

    const responseTime = Number((process.hrtime.bigint() - startedAt) / 1000000n);
    if (error) {
      return {
        id,
        isUp: false,
        statusCode: 502,
        responseTime,
        error: error.message
      };
    }

    return {
      id,
      isUp: isServiceUp(statusCode),
      statusCode,
      responseTime,
      url
    };
  }

  async function mapWithConcurrency(items, limit, worker) {
    const results = new Array(items.length);
    let cursor = 0;

    async function next() {
      while (true) {
        const index = cursor++;
        if (index >= items.length) return;
        results[index] = await worker(items[index], index);
      }
    }

    const workers = Array.from({ length: Math.min(limit, items.length || 1) }, () => next());
    await Promise.all(workers);
    return results;
  }

  // ===== SERVICE CREDENTIAL ENDPOINTS =====

  // Store credentials for a service
  router.post('/services/:serviceId/credentials', ctx.asyncHandler(async (req, res) => {
    const { serviceId } = req.params;
    const { apiKey, username, password } = req.body;

    if (apiKey) {
      await ctx.credentialManager.store(`service.${serviceId}.apikey`, apiKey);
    }
    if (username) {
      await ctx.credentialManager.store(`service.${serviceId}.username`, username);
    }
    if (password) {
      await ctx.credentialManager.store(`service.${serviceId}.password`, password);
    }

    res.json({ success: true, message: `Credentials stored for ${serviceId}` });
  }, 'store-service-creds'));

  // Delete credentials for a service
  router.delete('/services/:serviceId/credentials', ctx.asyncHandler(async (req, res) => {
    const { serviceId } = req.params;
    await ctx.credentialManager.delete(`service.${serviceId}.apikey`);
    await ctx.credentialManager.delete(`service.${serviceId}.username`);
    await ctx.credentialManager.delete(`service.${serviceId}.password`);
    res.json({ success: true, message: `Credentials removed for ${serviceId}` });
  }, 'delete-service-creds'));

  // Check credential status for a service (what's stored)
  router.get('/services/:serviceId/credentials', ctx.asyncHandler(async (req, res) => {
    try {
      const { serviceId } = req.params;
      const arrKey = await ctx.credentialManager.retrieve(`arr.${serviceId}.apikey`).catch(() => null);
      const svcKey = await ctx.credentialManager.retrieve(`service.${serviceId}.apikey`).catch(() => null);
      const username = await ctx.credentialManager.retrieve(`service.${serviceId}.username`).catch(() => null);
      res.json({
        success: true,
        hasApiKey: !!(arrKey || svcKey),
        hasBasicAuth: !!username,
        username: username || null
      });
    } catch (error) {
      res.json({ success: true, hasApiKey: false, hasBasicAuth: false });
    }
  }, 'service-creds'));

  // ===== SEEDHOST CREDENTIAL ENDPOINTS =====

  // Store seedhost credentials (shared username + per-service passwords)
  router.post('/seedhost-creds', ctx.asyncHandler(async (req, res) => {
    const { username, password, serviceId } = req.body;
    if (!username) {
      return ctx.errorResponse(res, 400, 'Username required');
    }
    await ctx.credentialManager.store('seedhost.username', username);
    if (password) {
      if (serviceId) {
        await ctx.credentialManager.store(`seedhost.password.${serviceId}`, password);
      } else {
        await ctx.credentialManager.store('seedhost.password', password);
      }
    }
    res.json({ success: true, message: 'Seedhost credentials stored' });
  }, 'store-seedhost-creds'));

  // Get seedhost credential status
  router.get('/seedhost-creds', ctx.asyncHandler(async (req, res) => {
    try {
      const username = await ctx.credentialManager.retrieve('seedhost.username').catch(() => null);
      const serviceId = req.query.serviceId;
      let hasPassword = false;
      if (serviceId) {
        const svcPass = await ctx.credentialManager.retrieve(`seedhost.password.${serviceId}`).catch(() => null);
        hasPassword = !!svcPass;
      }
      // Fall back to checking shared password
      if (!hasPassword) {
        const sharedPass = await ctx.credentialManager.retrieve('seedhost.password').catch(() => null);
        hasPassword = !!sharedPass;
      }
      res.json({ success: true, hasCredentials: !!username && hasPassword, username: username || null, hasPassword });
    } catch (error) {
      res.json({ success: true, hasCredentials: false });
    }
  }, 'seedhost-creds'));

  // Delete seedhost credentials
  router.delete('/seedhost-creds', ctx.asyncHandler(async (req, res) => {
    const serviceId = req.query.serviceId;
    if (serviceId) {
      await ctx.credentialManager.delete(`seedhost.password.${serviceId}`);
      res.json({ success: true, message: `Password for ${serviceId} removed` });
    } else {
      await ctx.credentialManager.delete('seedhost.username');
      await ctx.credentialManager.delete('seedhost.password');
      res.json({ success: true, message: 'Seedhost credentials removed' });
    }
  }, 'delete-seedhost-creds'));

  // ===== SERVICE CRUD ENDPOINTS =====

  // Batched live status for dashboard cards
  router.get('/services/status', ctx.asyncHandler(async (req, res) => {
    const services = await loadServicesList();
    const serviceMap = new Map(services.filter(s => s && s.id).map(s => [s.id, s]));
    const ids = [];
    const seen = new Set();

    function addId(id) {
      if (!id || seen.has(id)) return;
      seen.add(id);
      ids.push(id);
    }

    addId('internet');
    Object.keys(ctx.siteConfig?.dnsServers || {}).forEach(addId);
    services.forEach(service => addId(service.id));

    const statusResults = await mapWithConcurrency(ids, PROBE_CONCURRENCY, (id) =>
      probeServiceStatus(id, serviceMap.get(id))
    );

    const statuses = {};
    statusResults.forEach((result) => {
      statuses[result.id] = result;
    });

    res.set('Cache-Control', 'no-store');
    res.json({
      success: true,
      checkedAt: new Date().toISOString(),
      statuses
    });
  }, 'services-status'));

  // List all services
  router.get('/services', ctx.asyncHandler(async (req, res) => {
    if (!await exists(ctx.SERVICES_FILE)) {
      return res.json([]);
    }
    const services = await ctx.servicesStateManager.read();
    const paginationParams = parsePaginationParams(req.query);
    const result = paginate(services, paginationParams);
    if (paginationParams) {
      res.json({ success: true, services: result.data, pagination: result.pagination });
    } else {
      res.json(result.data);
    }
  }, 'services-list'));

  // Add a new service
  router.post('/services', ctx.asyncHandler(async (req, res) => {
    try {
      const { id, name, logo } = req.body;

      if (!id || !name) {
        return ctx.errorResponse(res, 400, 'id and name are required');
      }

      // Validate service configuration
      try {
        validateServiceConfig({ id, name });
      } catch (validationErr) {
        return ctx.errorResponse(res, 400, validationErr.message, { errors: validationErr.errors });
      }

      await ctx.servicesStateManager.update(services => {
        // Check if service already exists
        if (services.find(s => s.id === id)) {
          throw new Error(`Service "${id}" already exists`);
        }

        services.push({ id, name, logo: logo || `/assets/${id}.png` });
        return services;
      });

      ctx.resyncHealthChecker?.().catch(() => {});
      res.json({ success: true, message: `Service "${name}" added to dashboard` });
    } catch (error) {
      ctx.log.error('deploy', 'Error adding service', { error: error.message });
      if (error.message.includes('already exists')) {
        ctx.errorResponse(res, 409, ctx.safeErrorMessage(error));
      } else {
        ctx.errorResponse(res, 500, ctx.safeErrorMessage(error));
      }
    }
  }, 'services-update'));

  // Bulk import/replace services (for dashboard import feature)
  router.put('/services', ctx.asyncHandler(async (req, res) => {
    const services = req.body;

    if (!Array.isArray(services)) {
      return ctx.errorResponse(res, 400, 'Request body must be an array of services');
    }

    for (const service of services) {
      if (!service.id || !service.name) {
        return ctx.errorResponse(res, 400, 'Each service must have id and name fields');
      }
      try {
        validateServiceConfig(service);
      } catch (validationErr) {
        return ctx.errorResponse(res, 400, `Invalid service "${service.id}": ${validationErr.message}`, { errors: validationErr.errors });
      }
    }

    await ctx.servicesStateManager.write(services);
    ctx.resyncHealthChecker?.().catch(() => {});

    res.json({
      success: true,
      message: `Successfully imported ${services.length} services`,
      count: services.length
    });
  }, 'services-import'));

  // Delete a service
  router.delete('/services/:id', ctx.asyncHandler(async (req, res) => {
    const { id } = req.params;

    if (!await exists(ctx.SERVICES_FILE)) {
      return ctx.errorResponse(res, 404, 'No services found');
    }

    let found = false;
    await ctx.servicesStateManager.update(services => {
      const initialLength = services.length;
      const filtered = services.filter(s => s.id !== id);
      found = filtered.length !== initialLength;
      return filtered;
    });

    if (!found) {
      return ctx.errorResponse(res, 404, `Service "${id}" not found`);
    }

    ctx.resyncHealthChecker?.().catch(() => {});
    res.json({ success: true, message: `Service "${id}" removed from dashboard` });
  }, 'services-delete'));

  // Update service configuration (subdomain, port, IP, tailscale)
  router.post('/services/update', ctx.asyncHandler(async (req, res) => {
    const { oldSubdomain, newSubdomain, port, ip, tailscaleOnly } = req.body;

    if (!oldSubdomain || !newSubdomain) {
      return ctx.errorResponse(res, 400, 'oldSubdomain and newSubdomain are required');
    }

    if (!REGEX.SUBDOMAIN.test(oldSubdomain) || !REGEX.SUBDOMAIN.test(newSubdomain)) {
      return ctx.errorResponse(res, 400, '[DC-301] Invalid subdomain format');
    }

    if (port && !isValidPort(port)) {
      return ctx.errorResponse(res, 400, 'Invalid port number (must be 1-65535)');
    }

    if (ip && !validatorLib.isIP(ip)) {
      return ctx.errorResponse(res, 400, '[DC-210] Invalid IP address');
    }

    const results = { dns: null, caddy: null, services: null };

    const oldDomain = ctx.buildDomain(oldSubdomain);
    const newDomain = ctx.buildDomain(newSubdomain);

    let content = await ctx.caddy.read();

    const escapedOldDomain = oldDomain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    const siteBlockRegex = new RegExp(
      `${escapedOldDomain}\\s*\\{[^{}]*(?:\\{[^{}]*(?:\\{[^{}]*\\}[^{}]*)*\\}[^{}]*)*\\}`,
      's'
    );

    const oldBlockMatch = content.match(siteBlockRegex);
    if (oldBlockMatch) {
      const proxyMatch = oldBlockMatch[0].match(/reverse_proxy\s+([^\s\n]+)/);
      const existingTarget = proxyMatch ? proxyMatch[1] : null;
      const [existingIp, existingPort] = existingTarget ? existingTarget.split(':') : ['localhost', '80'];

      const finalIp = ip || existingIp;
      const finalPort = port || existingPort;

      const newConfig = ctx.caddy.generateConfig(newSubdomain, finalIp, finalPort, {
        tailscaleOnly: tailscaleOnly || false
      });

      const caddyResult = await ctx.caddy.modify(c => c.replace(siteBlockRegex, newConfig));
      results.caddy = caddyResult.success ? 'updated' : `config saved, reload failed: ${caddyResult.error}`;
    } else {
      results.caddy = 'old config not found';
    }

    if (oldSubdomain !== newSubdomain) {
      try {
        const dnsToken = ctx.dns.getToken();
        await ctx.dns.call(ctx.siteConfig.dnsServerIp, '/api/zones/records/delete', { token: dnsToken, domain: oldDomain, type: 'A' });
        await ctx.dns.createRecord(newSubdomain, ip || 'localhost');
        results.dns = 'updated';
      } catch (e) {
        results.dns = `failed: ${e.message}`;
      }
    } else {
      results.dns = 'unchanged';
    }

    if (await exists(ctx.SERVICES_FILE)) {
      await ctx.servicesStateManager.update(services => {
        const serviceIndex = services.findIndex(s => s.id === oldSubdomain);
        if (serviceIndex !== -1) {
          services[serviceIndex] = {
            ...services[serviceIndex],
            id: newSubdomain,
            port: port || services[serviceIndex].port,
            ip: ip || services[serviceIndex].ip,
            tailscaleOnly: tailscaleOnly || false
          };
          results.services = 'updated';
        } else {
          results.services = 'not found';
        }
        return services;
      });
    }

    ctx.resyncHealthChecker?.().catch(() => {});
    res.json({
      success: true,
      message: `Service updated: ${oldSubdomain} -> ${newSubdomain}`,
      results
    });
  }, 'services-update'));

  return router;
};
