const express = require('express');
const fs = require('fs');
const { TAILSCALE } = require('../src/utilities/constants');
const { exists } = require('../src/utilities/fs-helpers');
const { ValidationError, NotFoundError } = require('../src/utilities/errors');
const { ok, successMessage, unauthorized } = require('../src/utils/responses');

/**
 * Tailscale route factory
 * @param {Object} deps - Explicit dependencies
 * @param {Object} deps.tailscale - Tailscale manager
 * @param {Object} deps.caddy - Caddy manager
 * @param {Object} deps.servicesStateManager - Services state manager
 * @param {Object} deps.credentialManager - Credential manager
 * @param {Function} deps.buildDomain - Domain builder function
 * @param {Function} deps.asyncHandler - Async route handler wrapper
 * @param {Function} deps.ok - Success response helper
 * @param {string} deps.SERVICES_FILE - Path to services.json
 * @param {Object} deps.log - Logger instance
 * @returns {express.Router}
 */
module.exports = function({
  tailscale,
  caddy,
  servicesStateManager,
  credentialManager,
  buildDomain,
  asyncHandler,
  ok,
  SERVICES_FILE,
  log
}) {
  const router = express.Router();

  // Get Tailscale status and configuration
  router.get('/status', asyncHandler(async (req, res) => {
    const status = await tailscale.getStatus();
    const localIP = await tailscale.getLocalIP();

    if (!status) {
      return ok(res, {
        installed: false,
        connected: false,
        message: 'Tailscale not available or not running'
      });
    }

    const devices = [];
    if (status.Peer) {
      for (const [id, peer] of Object.entries(status.Peer)) {
        devices.push({
          id,
          hostname: peer.HostName,
          ip: peer.TailscaleIPs?.[0],
          os: peer.OS,
          online: peer.Online,
          lastSeen: peer.LastSeen,
          user: peer.UserID
        });
      }
    }

    ok(res, {
      installed: true,
      connected: status.BackendState === 'Running',
      backendState: status.BackendState,
      self: {
        hostname: status.Self?.HostName,
        ip: localIP,
        tailnetName: status.MagicDNSSuffix,
        online: status.Self?.Online
      },
      config: tailscale.config,
      devices,
      deviceCount: devices.length
    });
  }, 'tailscale-status'));

  // Update Tailscale configuration
  router.post('/config', asyncHandler(async (req, res) => {
    const { enabled, requireAuth, allowedTailnet } = req.body;

    if (typeof enabled !== 'undefined') tailscale.config.enabled = enabled;
    if (typeof requireAuth !== 'undefined') tailscale.config.requireAuth = requireAuth;
    if (typeof allowedTailnet !== 'undefined') tailscale.config.allowedTailnet = allowedTailnet;

    await tailscale.save();

    ok(res, {
      message: 'Tailscale configuration updated',
      config: tailscale.config
    });
  }, 'tailscale-config'));

  // Check if a request is coming from Tailscale
  router.get('/check-connection', asyncHandler(async (req, res) => {
    const clientIP = req.ip || req.connection?.remoteAddress || '';
    const forwardedFor = req.headers['x-forwarded-for'];
    const realIP = req.headers['x-real-ip'];

    const ipsToCheck = [clientIP, forwardedFor, realIP].filter(Boolean);
    const isTailscale = ipsToCheck.some(ip => tailscale.isTailscaleIP(ip.toString().split(',')[0].trim()));

    ok(res, {
      isTailscale,
      clientIP,
      forwardedFor: forwardedFor || null,
      realIP: realIP || null
    });
  }, 'tailscale-check'));

  // Get Tailscale device list
  router.get('/devices', asyncHandler(async (req, res) => {
    const status = await tailscale.getStatus();
    if (!status || !status.Peer) {
      return ok(res, { devices: [] });
    }

    const devices = [];
    for (const [id, peer] of Object.entries(status.Peer)) {
      if (peer.Online) {
        devices.push({
          id,
          hostname: peer.HostName,
          ip: peer.TailscaleIPs?.[0],
          os: peer.OS,
          user: peer.UserID
        });
      }
    }

    if (status.Self) {
      devices.unshift({
        id: 'self',
        hostname: status.Self.HostName,
        ip: status.Self.TailscaleIPs?.[0],
        os: status.Self.OS,
        user: status.Self.UserID,
        isSelf: true
      });
    }

    ok(res, { devices });
  }, 'tailscale-devices'));

  // Toggle Tailscale-only mode for an existing service
  router.post('/protect-service', asyncHandler(async (req, res) => {
    const { subdomain, tailscaleOnly, allowedIPs } = req.body;

    if (!subdomain) {
      throw new ValidationError('subdomain is required');
    }

    const content = await caddy.read();
    const domain = buildDomain(subdomain);

    const blockRegex = new RegExp(`(${domain.replace('.', '\\.')}\\s*\\{[^}]*\\})`, 's');
    const match = content.match(blockRegex);

    if (!match) {
      const { NotFoundError } = require('../src/utilities/errors');
      throw new NotFoundError(`Service ${domain} in Caddyfile`);
    }

    const proxyMatch = match[0].match(/reverse_proxy\s+([^\s\n]+)/);
    if (!proxyMatch) {
      throw new ValidationError('Could not parse service configuration');
    }

    const [ip, port] = proxyMatch[1].split(':');

    const newConfig = caddy.generateConfig(subdomain, ip, port || '80', {
      tailscaleOnly: tailscaleOnly !== false,
      allowedIPs: allowedIPs || []
    });

    const caddyResult = await caddy.modify(c => c.replace(blockRegex, newConfig));
    if (!caddyResult.success) {
      throw new Error(`Failed to reload Caddy: ${caddyResult.error}`);
    }

    if (await exists(SERVICES_FILE)) {
      await servicesStateManager.update(services => {
        const serviceIndex = services.findIndex(s => s.id === subdomain);
        if (serviceIndex !== -1) {
          services[serviceIndex].tailscaleOnly = tailscaleOnly !== false;
        }
        return services;
      });
    }

    ok(res, {
      message: `Service ${domain} is now ${tailscaleOnly !== false ? 'protected by' : 'no longer restricted to'} Tailscale`,
      tailscaleOnly: tailscaleOnly !== false
    });
  }, 'tailscale-protect'));

  // ── Tailscale API Integration (OAuth 2.0) ──

  // Save OAuth client credentials + validate by exchanging for a token
  router.post('/oauth-config', asyncHandler(async (req, res) => {
    const { clientId, clientSecret, tailnet } = req.body;

    if (!clientId || !clientSecret || !tailnet) {
      throw new ValidationError('clientId, clientSecret, and tailnet are required');
    }

    // Validate by exchanging for a real token
    const tokenRes = await fetch(TAILSCALE.OAUTH_TOKEN_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: `client_id=${encodeURIComponent(clientId)}&client_secret=${encodeURIComponent(clientSecret)}&grant_type=client_credentials`
    });

    if (!tokenRes.ok) {
      throw new ValidationError(`OAuth validation failed: HTTP ${tokenRes.status}`);
    }

    const tokenData = await tokenRes.json();

    // Test with the device list to verify scopes
    const testRes = await fetch(`${TAILSCALE.API_BASE}/tailnet/${encodeURIComponent(tailnet)}/devices`, {
      headers: { Authorization: `Bearer ${tokenData.access_token}` }
    });

    if (!testRes.ok) {
      throw new ValidationError(`API test failed: HTTP ${testRes.status}. Check tailnet name and OAuth scopes (needs devices:read, acl:read).`);
    }

    // Store credentials securely
    await credentialManager.store('tailscale.oauth.client_id', clientId, { provider: 'tailscale' });
    await credentialManager.store('tailscale.oauth.client_secret', clientSecret, { provider: 'tailscale', tailnet });

    // Update config
    tailscale.config.oauthConfigured = true;
    tailscale.config.tailnet = tailnet;
    if (!tailscale.config.allowedTailnet) {
      const status = await tailscale.getStatus();
      if (status?.MagicDNSSuffix) {
        tailscale.config.allowedTailnet = status.MagicDNSSuffix;
      }
    }
    await tailscale.save();

    // Start background sync
    tailscale.startSync();

    // Trigger initial sync
    try {
      await tailscale.syncAPI();
    } catch (e) {
      log.warn('tailscale', 'Initial sync after OAuth config failed', { error: e.message });
    }

    ok(res, { config: tailscale.config });
  }, 'tailscale-oauth-config'));

  // Remove OAuth credentials and disable API sync
  router.delete('/oauth-config', asyncHandler(async (req, res) => {
    await credentialManager.delete('tailscale.oauth.client_id');
    await credentialManager.delete('tailscale.oauth.client_secret');

    tailscale.config.oauthConfigured = false;
    tailscale.config.tailnet = null;
    tailscale.config.lastSync = null;
    await tailscale.save();

    tailscale.stopSync();

    successMessage(res, 'Tailscale OAuth credentials removed');
  }, 'tailscale-oauth-delete'));

  // Get enriched device list from Tailscale API
  router.get('/api-devices', asyncHandler(async (req, res) => {
    if (!tailscale.config.oauthConfigured) {
      throw new ValidationError('Tailscale API not configured. Set up OAuth first.');
    }

    // Return cached devices from last sync
    ok(res, {
      devices: tailscale.config.devices || [],
      lastSync: tailscale.config.lastSync
    });
  }, 'tailscale-api-devices'));

  // Manually trigger an API sync
  router.post('/sync', asyncHandler(async (req, res) => {
    if (!tailscale.config.oauthConfigured) {
      throw new ValidationError('Tailscale API not configured. Set up OAuth first.');
    }

    const devices = await tailscale.syncAPI();

    ok(res, {
      devices: devices || [],
      lastSync: tailscale.config.lastSync
    });
  }, 'tailscale-sync'));

  // Fetch ACL policy (read-only)
  router.get('/acl', asyncHandler(async (req, res) => {
    const token = await tailscale.getAccessToken();
    const tailnet = tailscale.config.tailnet;
    if (!token || !tailnet) {
      throw new ValidationError('Tailscale API not configured');
    }

    const aclRes = await fetch(`${TAILSCALE.API_BASE}/tailnet/${encodeURIComponent(tailnet)}/acl`, {
      headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' }
    });
    if (!aclRes.ok) {
      throw new Error(`ACL fetch failed: HTTP ${aclRes.status}`);
    }

    const acl = await aclRes.json();

    const summary = {
      groups: Object.keys(acl.groups || {}),
      tagOwners: Object.keys(acl.tagOwners || {}),
      aclRuleCount: (acl.acls || []).length,
      sshRuleCount: (acl.ssh || []).length
    };

    ok(res, { acl, summary });
  }, 'tailscale-acl'));

  return router;
};
