const express = require('express');
const { DOCKER } = require('../../src/utilities/constants');
const { NotFoundError } = require('../../src/utilities/errors');
const { ok } = require('../../src/utils/responses');

module.exports = function({ servicesStateManager, asyncHandler, log, docker, notification, buildDomain, caddy }) {
  const router = express.Router();

  // Ctx shim for backward compatibility
  const ctx = { docker, notification, buildDomain, caddy };

/**
 * Recipes management routes factory
 * @param {Object} deps - Explicit dependencies
 * @param {Object} deps.servicesStateManager - Services state manager
 * @param {Function} deps.asyncHandler - Async route handler wrapper
 * @param {Object} deps.log - Logger instance
 * @returns {express.Router}
 */

  /**
   * GET /api/recipes/deployed — list all deployed recipes (grouped by recipeId)
   */
  router.get('/deployed', asyncHandler(async (req, res) => {
    const services = await servicesStateManager.read();
    const recipeGroups = {};

    for (const service of services) {
      if (!service.recipeId) continue;
      if (!recipeGroups[service.recipeId]) {
        recipeGroups[service.recipeId] = {
          recipeId: service.recipeId,
          components: []
        };
      }
      recipeGroups[service.recipeId].components.push({
        id: service.id,
        name: service.name,
        logo: service.logo,
        containerId: service.containerId,
        recipeRole: service.recipeRole,
        deployedAt: service.deployedAt
      });
    }

    // Also find internal containers (not in services.json) by Docker labels
    try {
      const containers = await ctx.docker.client.listContainers({ all: true });
      for (const containerInfo of containers) {
        const labels = containerInfo.Labels || {};
        if (labels['sami.managed'] !== 'true') continue;
        const recipeLabel = labels['sami.recipe'];
        if (!recipeLabel) continue;

        // Map recipe label back to recipe ID
        const recipeId = findRecipeIdByLabel(recipeLabel);
        if (!recipeId) continue;

        if (!recipeGroups[recipeId]) {
          recipeGroups[recipeId] = { recipeId, components: [] };
        }

        // Check if this container is already listed (by containerId)
        const existing = recipeGroups[recipeId].components.find(
          c => c.containerId === containerInfo.Id
        );
        if (existing) continue;

        recipeGroups[recipeId].components.push({
          id: labels['sami.recipe.component'] || containerInfo.Names[0]?.replace('/', ''),
          name: labels['sami.recipe.role'] || labels['sami.app'] || 'Unknown',
          containerId: containerInfo.Id,
          recipeRole: labels['sami.recipe.role'] || 'Unknown',
          internal: true,
          state: containerInfo.State,
          status: containerInfo.Status
        });
      }
    } catch (e) {
      log.warn('recipe', 'Could not list Docker containers for recipe discovery', { error: e.message });
    }

    // Enrich with container state
    for (const group of Object.values(recipeGroups)) {
      for (const component of group.components) {
        if (component.containerId && !component.state) {
          try {
            const container = ctx.docker.client.getContainer(component.containerId);
            const info = await container.inspect();
            component.state = info.State.Status;
            component.status = info.State.Status === 'running'
              ? `Up ${formatUptime(info.State.StartedAt)}`
              : info.State.Status;
          } catch (e) {
            component.state = 'removed';
            component.status = 'Container not found';
          }
        }
      }
    }

    ok(res, { recipes: Object.values(recipeGroups) });
  }, 'recipe-deployed'));

  /**
   * POST /api/recipes/:recipeId/start — start all containers in a recipe
   */
  router.post('/:recipeId/start', asyncHandler(async (req, res) => {
    const { recipeId } = req.params;
    const containers = await findRecipeContainers(recipeId);

    if (containers.length === 0) {
      throw new NotFoundError('Containers for recipe');
    }

    const results = [];
    for (const containerInfo of containers) {
      try {
        const container = ctx.docker.client.getContainer(containerInfo.Id);
        const info = await container.inspect();
        if (info.State.Status !== 'running') {
          await container.start();
          results.push({ id: containerInfo.component, status: 'started' });
        } else {
          results.push({ id: containerInfo.component, status: 'already running' });
        }
      } catch (e) {
        results.push({ id: containerInfo.component, status: 'failed', error: e.message });
      }
    }

    log.info('recipe', 'Recipe started', { recipeId, results });
    ok(res, { recipeId, results });
  }, 'recipe-start'));

  /**
   * POST /api/recipes/:recipeId/stop — stop all containers in a recipe
   */
  router.post('/:recipeId/stop', asyncHandler(async (req, res) => {
    const { recipeId } = req.params;
    const containers = await findRecipeContainers(recipeId);

    if (containers.length === 0) {
      throw new NotFoundError('Containers for recipe');
    }

    const results = [];
    // Stop in reverse order (apps first, then infrastructure)
    for (const containerInfo of containers.reverse()) {
      try {
        const container = ctx.docker.client.getContainer(containerInfo.Id);
        const info = await container.inspect();
        if (info.State.Status === 'running') {
          await container.stop();
          results.push({ id: containerInfo.component, status: 'stopped' });
        } else {
          results.push({ id: containerInfo.component, status: 'already stopped' });
        }
      } catch (e) {
        results.push({ id: containerInfo.component, status: 'failed', error: e.message });
      }
    }

    log.info('recipe', 'Recipe stopped', { recipeId, results });
    ok(res, { recipeId, results });
  }, 'recipe-stop'));

  /**
   * POST /api/recipes/:recipeId/restart — restart all containers in a recipe
   */
  router.post('/:recipeId/restart', asyncHandler(async (req, res) => {
    const { recipeId } = req.params;
    const containers = await findRecipeContainers(recipeId);

    if (containers.length === 0) {
      throw new NotFoundError('Containers for recipe');
    }

    const results = [];
    for (const containerInfo of containers) {
      try {
        const container = ctx.docker.client.getContainer(containerInfo.Id);
        await container.restart();
        results.push({ id: containerInfo.component, status: 'restarted' });
      } catch (e) {
        results.push({ id: containerInfo.component, status: 'failed', error: e.message });
      }
    }

    log.info('recipe', 'Recipe restarted', { recipeId, results });
    ok(res, { recipeId, results });
  }, 'recipe-restart'));

  /**
   * DELETE /api/recipes/:recipeId — remove entire recipe (containers, network, services)
   */
  router.delete('/:recipeId', asyncHandler(async (req, res) => {
    const { recipeId } = req.params;
    const containers = await findRecipeContainers(recipeId);

    if (containers.length === 0) {
      throw new NotFoundError('Containers for recipe');
    }

    log.info('recipe', 'Removing recipe', { recipeId, containerCount: containers.length });

    const results = [];
    const networkNames = new Set();

    // Remove containers (reverse order: apps first, then infrastructure)
    for (const containerInfo of containers.reverse()) {
      try {
        const container = ctx.docker.client.getContainer(containerInfo.Id);
        const info = await container.inspect();

        // Collect network names for cleanup
        for (const netName of Object.keys(info.NetworkSettings.Networks || {})) {
          if (netName.startsWith('dashcaddy-')) {
            networkNames.add(netName);
          }
        }

        // Remove Caddy config for this subdomain
        const subdomain = info.Config?.Labels?.['sami.subdomain'];
        if (subdomain) {
          try {
            await removeCaddyBlock(subdomain);
          } catch (e) {
            log.warn('recipe', 'Failed to remove Caddy config', { subdomain, error: e.message });
          }
        }

        // Force remove container
        await container.remove({ force: true });
        results.push({ id: containerInfo.component, status: 'removed' });
      } catch (e) {
        results.push({ id: containerInfo.component, status: 'failed', error: e.message });
      }
    }

    // Remove recipe services from services.json
    await servicesStateManager.update(services => {
      return services.filter(s => s.recipeId !== recipeId);
    });

    // Remove Docker networks
    for (const netName of networkNames) {
      try {
        const network = ctx.docker.client.getNetwork(netName);
        await network.remove();
        log.info('recipe', 'Removed Docker network', { netName });
      } catch (e) {
        log.warn('recipe', 'Failed to remove network', { netName, error: e.message });
      }
    }

    ctx.notification.send('recipeRemoved', 'Recipe Removed',
      `Removed **${recipeId}** recipe (${results.filter(r => r.status === 'removed').length} containers).`,
      'info'
    );

    log.info('recipe', 'Recipe removed', { recipeId, results });
    ok(res, { recipeId, results });
  }, 'recipe-remove'));

  // === Helper functions ===

  /**
   * Find all Docker containers belonging to a recipe by label
   */
  async function findRecipeContainers(recipeId) {
    const { RECIPE_TEMPLATES } = require('../../src/recipes/recipe-templates');
    const recipe = RECIPE_TEMPLATES[recipeId];
    const recipeLabel = recipe
      ? recipe.name.toLowerCase().replace(/\s+/g, '-')
      : recipeId;

    const containers = await ctx.docker.client.listContainers({ all: true });
    return containers
      .filter(c => {
        const labels = c.Labels || {};
        return labels['sami.managed'] === 'true' && labels['sami.recipe'] === recipeLabel;
      })
      .map(c => ({
        Id: c.Id,
        component: c.Labels['sami.recipe.component'] || c.Names[0]?.replace('/', ''),
        role: c.Labels['sami.recipe.role'] || 'Unknown',
        state: c.State
      }));
  }

  /**
   * Find recipe ID by its label (name slug)
   */
  function findRecipeIdByLabel(label) {
    const { RECIPE_TEMPLATES } = require('../../src/recipes/recipe-templates');
    for (const [id, recipe] of Object.entries(RECIPE_TEMPLATES)) {
      if (recipe.name.toLowerCase().replace(/\s+/g, '-') === label) {
        return id;
      }
    }
    return null;
  }

  /**
   * Remove a Caddy block for a subdomain from the Caddyfile
   */
  async function removeCaddyBlock(subdomain) {
    const domain = ctx.buildDomain(subdomain);
    const content = await ctx.caddy.read();

    // Find and remove the block for this domain
    const escapedDomain = domain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    const blockRegex = new RegExp(`\\n?${escapedDomain}\\s*\\{[^}]*(?:\\{[^}]*\\}[^}]*)*\\}`, 'g');
    const newContent = content.replace(blockRegex, '');

    if (newContent !== content) {
      await ctx.caddy.write(newContent);
      await ctx.caddy.reload();
    }
  }

  /**
   * Format uptime from start time
   */
  function formatUptime(startedAt) {
    const seconds = Math.floor((Date.now() - new Date(startedAt).getTime()) / 1000);
    if (seconds < 60) return `${seconds}s`;
    if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
    if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`;
    return `${Math.floor(seconds / 86400)}d`;
  }

  return router;
};
