const express = require('express');
const { DOCKER } = require('../../constants');

/**
 * Apps restore routes factory
 * @param {Object} deps - Explicit dependencies
 * @param {Object} deps.docker - Docker client wrapper
 * @param {Object} deps.caddy - Caddy client
 * @param {Object} deps.servicesStateManager - Services state manager
 * @param {Function} deps.asyncHandler - Async route handler wrapper
 * @param {Function} deps.errorResponse - Error response helper
 * @param {Object} deps.log - Logger instance
 * @param {Object} deps.helpers - Apps helpers module
 * @param {Object} deps.APP_TEMPLATES - App templates registry
 * @param {Object} deps.dns - DNS client
 * @param {Function} deps.buildServiceUrl - Service URL builder
 * @returns {express.Router}
 */
module.exports = function({ docker, caddy, servicesStateManager, asyncHandler, errorResponse, log, helpers, APP_TEMPLATES, dns, buildServiceUrl }) {
  const router = express.Router();

  const ctx = {
    APP_TEMPLATES,
    dns,
    buildServiceUrl
  };

  /**
   * Restore a single service from its deployment manifest.
   * Pulls image, creates container, starts it, recreates Caddy config.
   * Skips if container is already running.
   */
  router.post('/apps/:appId/restore', asyncHandler(async (req, res) => {
    const { appId } = req.params;
    const services = await servicesStateManager.read();
    const service = services.find(s => s.id === appId);

    if (!service) {
      return errorResponse(res, 404, `Service "${appId}" not found in services.json`);
    }
    if (!service.deploymentManifest) {
      return errorResponse(res, 400, `Service "${appId}" has no deployment manifest — it was deployed before the manifest feature was added. Redeploy it manually to create a manifest.`);
    }

    const result = await restoreService(service);
    res.json({ success: true, result });
  }, 'apps-restore'));

  /**
   * Restore all services that have deployment manifests.
   * Returns per-service results.
   */
  router.post('/apps/restore-all', asyncHandler(async (req, res) => {
    const services = await servicesStateManager.read();
    const restoreable = services.filter(s => s.deploymentManifest);

    if (restoreable.length === 0) {
      return res.json({
        success: true,
        message: 'No services have deployment manifests to restore',
        results: []
      });
    }

    const results = [];
    for (const service of restoreable) {
      try {
        const result = await restoreService(service);
        results.push(result);
      } catch (error) {
        results.push({
          id: service.id,
          name: service.name,
          status: 'failed',
          error: error.message
        });
      }
    }

    const succeeded = results.filter(r => r.status === 'restored').length;
    const skipped = results.filter(r => r.status === 'skipped').length;
    const failed = results.filter(r => r.status === 'failed').length;

    res.json({
      success: true,
      message: `Restore complete: ${succeeded} restored, ${skipped} skipped, ${failed} failed`,
      results
    });
  }, 'apps-restore-all'));

  /**
   * List all services and their restore status.
   */
  router.get('/apps/restore-status', asyncHandler(async (req, res) => {
    const services = await servicesStateManager.read();
    const status = [];

    for (const service of services) {
      const entry = {
        id: service.id,
        name: service.name,
        hasManifest: !!service.deploymentManifest,
        templateId: service.deploymentManifest?.templateId || service.appTemplate || null,
        deployedAt: service.deployedAt || null,
        containerRunning: false
      };

      // Check if container is currently running
      if (service.containerId) {
        try {
          const container = docker.client.getContainer(service.containerId);
          const info = await container.inspect();
          entry.containerRunning = info.State.Running;
        } catch (e) {
          entry.containerRunning = false;
        }
      }

      status.push(entry);
    }

    res.json({ success: true, services: status });
  }, 'apps-restore-status'));

  /**
   * Core restore logic for a single service.
   */
  async function restoreService(service) {
    const manifest = service.deploymentManifest;
    const template = ctx.APP_TEMPLATES[manifest.templateId];

    log.info('restore', `Restoring service: ${service.name}`, { id: service.id, templateId: manifest.templateId });

    // Static sites: just recreate Caddy config
    if (template?.isStaticSite) {
      log.info('restore', `Restoring static site Caddy config: ${service.name}`);
      // Static site Caddy config would need to be regenerated
      // For now, just confirm the service entry exists
      return {
        id: service.id,
        name: service.name,
        status: 'restored',
        type: 'static',
        message: `Static site "${service.name}" config preserved`
      };
    }

    // Docker container: check if already running
    if (service.containerId) {
      try {
        const existing = docker.client.getContainer(service.containerId);
        const info = await existing.inspect();
        if (info.State.Running) {
          log.info('restore', `Container already running, skipping: ${service.name}`);
          return {
            id: service.id,
            name: service.name,
            status: 'skipped',
            message: 'Container already running'
          };
        }
      } catch (e) {
        // Container doesn't exist — proceed with restore
      }
    }

    // Also check by name (container ID may have changed)
    const containerName = `${DOCKER.CONTAINER_PREFIX}${manifest.config.subdomain}`;
    try {
      const byName = docker.client.getContainer(containerName);
      const info = await byName.inspect();
      if (info.State.Running) {
        // Update the service entry with the current container ID
        await servicesStateManager.update(services => {
          const svc = services.find(s => s.id === service.id);
          if (svc) svc.containerId = info.Id;
          return services;
        });
        return {
          id: service.id,
          name: service.name,
          status: 'skipped',
          message: 'Container already running (found by name)'
        };
      }
      // Exists but not running — remove stale container
      await byName.remove({ force: true });
    } catch (e) {
      // Container doesn't exist — proceed
    }

    if (!manifest.container) {
      return {
        id: service.id,
        name: service.name,
        status: 'failed',
        error: 'No container configuration in manifest'
      };
    }

    // Pull image
    log.info('restore', `Pulling image: ${manifest.container.image}`);
    try {
      await docker.pull(manifest.container.image);
    } catch (e) {
      // Check if image exists locally
      const images = await docker.client.listImages({
        filters: { reference: [manifest.container.image] }
      });
      if (images.length === 0) {
        throw new Error(`Failed to pull image ${manifest.container.image}: ${e.message}`);
      }
      log.warn('restore', `Pull failed, using local image: ${manifest.container.image}`);
    }

    // Build container config from manifest
    const containerConfig = {
      Image: manifest.container.image,
      name: containerName,
      ExposedPorts: {},
      HostConfig: {
        PortBindings: {},
        Binds: manifest.container.volumes || [],
        RestartPolicy: { Name: 'unless-stopped' },
        LogConfig: DOCKER.LOG_CONFIG
      },
      Env: Object.entries(manifest.container.environment || {}).map(([k, v]) => `${k}=${v}`),
      Labels: {
        'sami.managed': 'true',
        'sami.app': manifest.templateId,
        'sami.subdomain': manifest.config.subdomain,
        'sami.deployed': new Date().toISOString(),
        'sami.restored': 'true'
      }
    };

    // Set up port bindings
    (manifest.container.ports || []).forEach(portMapping => {
      const [hostPort, containerPort, protocol = 'tcp'] = portMapping.split(/[:/]/);
      const containerPortKey = `${containerPort}/${protocol}`;
      containerConfig.ExposedPorts[containerPortKey] = {};
      containerConfig.HostConfig.PortBindings[containerPortKey] = [{ HostPort: hostPort }];
    });

    if (manifest.container.capabilities) {
      containerConfig.HostConfig.CapAdd = manifest.container.capabilities;
    }

    // Create and start container
    log.info('restore', `Creating container: ${containerName}`);
    const container = await docker.client.createContainer(containerConfig);
    await container.start();
    log.info('restore', `Container started: ${containerName}`);

    // Recreate Caddy config
    const port = manifest.config.port;
    const caddyOptions = {
      tailscaleOnly: manifest.caddy.tailscaleOnly,
      allowedIPs: manifest.caddy.allowedIPs,
      subpathSupport: manifest.caddy.subpathSupport,
    };

    if (manifest.caddy.routingMode === 'subdirectory') {
      const caddyConfig = caddy.generateConfig(manifest.config.subdomain, manifest.config.ip, port, caddyOptions);
      try {
        await helpers.ensureMainDomainBlock();
        await helpers.addSubpathConfig(manifest.config.subdomain, caddyConfig);
      } catch (e) {
        log.warn('restore', `Caddy config may already exist: ${e.message}`);
      }
    } else {
      const caddyConfig = caddy.generateConfig(manifest.config.subdomain, manifest.config.ip, port, caddyOptions);
      try {
        await helpers.addCaddyConfig(manifest.config.subdomain, caddyConfig);
      } catch (e) {
        log.warn('restore', `Caddy config may already exist: ${e.message}`);
      }
    }

    // DNS record
    if (manifest.config.createDns && manifest.caddy.routingMode !== 'subdirectory') {
      try {
        await ctx.dns.createRecord(manifest.config.subdomain, manifest.config.ip);
        log.info('restore', 'DNS record recreated', { subdomain: manifest.config.subdomain });
      } catch (e) {
        log.warn('restore', `DNS recreation failed: ${e.message}`);
      }
    }

    // Update the service entry with the new container ID
    await servicesStateManager.update(services => {
      const svc = services.find(s => s.id === service.id);
      if (svc) {
        svc.containerId = container.id;
        svc.url = buildServiceUrl(manifest.config.subdomain);
      }
      return services;
    });

    return {
      id: service.id,
      name: service.name,
      status: 'restored',
      type: 'container',
      containerId: container.id,
      message: `${service.name} restored successfully`
    };
  }

  return router;
};
