const { APP_PORTS } = require('../../src/utilities/constants');

/**
 * Arr helpers factory
 * @param {Object} deps - Explicit dependencies
 * @param {Object} deps.docker - Docker client wrapper
 * @param {Object} deps.credentialManager - Credential manager
 * @param {Function} deps.fetchT - Timeout-wrapped fetch
 * @param {Object} deps.log - Logger instance
 * @returns {Object} Helper functions
 */
module.exports = function({ docker, credentialManager, fetchT, log }) {

  // Helper: Extract API key from arr service config.xml
  async function getArrApiKey(containerName) {
    try {
      const container = await docker.findContainer(containerName);
      if (!container) return null;

      const dockerContainer = docker.client.getContainer(container.Id);
      const exec = await dockerContainer.exec({
        Cmd: ['cat', '/config/config.xml'],
        AttachStdout: true,
        AttachStderr: true
      });

      const stream = await exec.start();

      return new Promise((resolve) => {
        let data = '';
        stream.on('data', chunk => data += chunk.toString());
        stream.on('end', () => {
          // Extract API key from XML
          const match = data.match(/<ApiKey>([^<]+)<\/ApiKey>/);
          resolve(match ? match[1] : null);
        });
        stream.on('error', () => resolve(null));
      });
    } catch (error) {
      log.error('docker', 'Failed to get API key', { containerName, error: error.message });
      return null;
    }
  }

  // Helper: Get Plex token from container or config
  async function getPlexToken(containerName) {
    try {
      const containers = await docker.client.listContainers({ all: false });
      const container = containers.find(c =>
        c.Names.some(n => n.toLowerCase().includes(containerName.toLowerCase()) || n.toLowerCase().includes('plex'))
      );

      if (!container) return null;

      const dockerContainer = docker.client.getContainer(container.Id);
      const exec = await dockerContainer.exec({
        Cmd: ['cat', '/config/Library/Application Support/Plex Media Server/Preferences.xml'],
        AttachStdout: true,
        AttachStderr: true
      });

      const stream = await exec.start();

      return new Promise((resolve) => {
        let data = '';
        stream.on('data', chunk => data += chunk.toString());
        stream.on('end', () => {
          const match = data.match(/PlexOnlineToken="([^"]+)"/);
          resolve(match ? match[1] : null);
        });
        stream.on('error', () => resolve(null));
      });
    } catch (error) {
      log.error('docker', 'Failed to get Plex token', { error: error.message });
      return null;
    }
  }

  // Helper: Get container URL (internal Docker network or host)
  function getServiceUrl(containerName, port, useTailscale = false) {
    // For Docker containers, use localhost since they're on the same host
    const host = useTailscale ? (process.env.HOST_TAILSCALE_IP || 'localhost') : 'localhost';
    return `http://${host}:${port}`;
  }

  // Helper: Get authenticated Seerr/Overseerr session via Plex token
  // Seerr requires Plex-based auth for admin endpoints (settings, configuration)
  async function getOverseerrSession() {
    const seerrUrl = `http://host.docker.internal:${APP_PORTS.seerr}`;
    try {
      // Try getting Plex token from running container first
      let plexToken = await getPlexToken('plex');

      // Fall back to stored Plex token in credential manager
      if (!plexToken) {
        plexToken = await credentialManager.retrieve('arr.plex.token');
      }

      if (!plexToken) {
        log.error('arr', 'Could not get Plex token for Seerr auth (no container, no stored token)');
        return null;
      }

      // Authenticate with Seerr via Plex token
      const authRes = await fetchT(`${seerrUrl}/api/v1/auth/plex`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ authToken: plexToken }),
        signal: AbortSignal.timeout(10000)
      });

      if (!authRes.ok) {
        log.error('arr', 'Seerr Plex auth failed', { status: authRes.status });
        return null;
      }

      const setCookie = authRes.headers.get('set-cookie');
      if (!setCookie) {
        log.error('arr', 'No session cookie returned from Seerr');
        return null;
      }

      const sessionCookie = setCookie.split(';')[0];
      return { cookie: sessionCookie, plexToken };
    } catch (e) {
      log.error('arr', 'Could not get Seerr session', { error: e.message });
      return null;
    }
  }

  // Helper: Connect Plex to Overseerr
  // Uses session cookie auth (Overseerr requires Plex-based admin session for settings)
  async function connectPlexToOverseerr(plexUrl, plexToken, overseerrUrl, sessionCookie) {
    // 1. Get Plex server identity (for return info)
    const identityRes = await fetchT(`${plexUrl}/identity`, {
      headers: { 'X-Plex-Token': plexToken, 'Accept': 'application/json' },
      signal: AbortSignal.timeout(10000)
    });
    if (!identityRes.ok) throw new Error('Cannot reach Plex server');
    const identity = await identityRes.json();
    const serverName = identity.MediaContainer?.friendlyName || 'Plex';

    // 2. Configure Plex server connection in Overseerr
    // Only send writable fields — name, machineId, libraries are read-only (auto-discovered by Overseerr)
    const plexConfig = {
      ip: 'host.docker.internal',
      port: APP_PORTS.plex,
      useSsl: false
    };

    const configRes = await fetchT(`${overseerrUrl}/api/v1/settings/plex`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Cookie': sessionCookie
      },
      body: JSON.stringify(plexConfig)
    });

    if (!configRes.ok) {
      throw new Error(`Overseerr Plex config failed: ${await configRes.text()}`);
    }

    // 3. Trigger library sync — Overseerr will use the admin's Plex token to discover libraries
    try {
      await fetchT(`${overseerrUrl}/api/v1/settings/plex/sync`, {
        method: 'POST',
        headers: { 'Cookie': sessionCookie },
        signal: AbortSignal.timeout(10000)
      });
    } catch (e) {
      log.warn('arr', 'Plex library sync trigger failed (non-fatal)', { error: e.message });
    }

    // 4. Get discovered libraries
    let libraries = [];
    try {
      const libRes = await fetchT(`${overseerrUrl}/api/v1/settings/plex`, {
        headers: { 'Cookie': sessionCookie },
        signal: AbortSignal.timeout(5000)
      });
      if (libRes.ok) {
        const plexSettings = await libRes.json();
        libraries = plexSettings.libraries || [];
      }
    } catch (e) { /* non-fatal */ }

    return { success: true, libraries, serverName, machineId: identity.MediaContainer?.machineIdentifier };
  }

  // Helper: Configure Prowlarr connected apps (Radarr/Sonarr)
  async function configureProwlarrApps(prowlarrUrl, prowlarrApiKey, apps) {
    const results = {};

    // Check existing apps to avoid duplicates
    let existingApps = [];
    try {
      const existingRes = await fetchT(`${prowlarrUrl}/api/v1/applications`, {
        headers: { 'X-Api-Key': prowlarrApiKey },
        signal: AbortSignal.timeout(10000)
      });
      existingApps = existingRes.ok ? await existingRes.json() : [];
    } catch (e) {
      log.warn('arr', 'Could not fetch existing Prowlarr apps', { error: e.message });
    }

    for (const [appName, config] of Object.entries(apps)) {
      const implementation = appName.charAt(0).toUpperCase() + appName.slice(1); // "Radarr", "Sonarr"

      // Skip if already configured
      if (existingApps.some(a => a.implementation === implementation)) {
        results[appName] = 'already_configured';
        continue;
      }

      const syncCategories = appName === 'radarr'
        ? [2000, 2010, 2020, 2030, 2040, 2045, 2050, 2060]
        : [5000, 5010, 5020, 5030, 5040, 5045, 5050];

      const payload = {
        name: implementation,
        syncLevel: 'fullSync',
        implementation: implementation,
        configContract: `${implementation}Settings`,
        fields: [
          { name: 'prowlarrUrl', value: prowlarrUrl },
          { name: 'baseUrl', value: config.url },
          { name: 'apiKey', value: config.apiKey },
          { name: 'syncCategories', value: syncCategories }
        ]
      };

      try {
        const res = await fetchT(`${prowlarrUrl}/api/v1/applications`, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'X-Api-Key': prowlarrApiKey
          },
          body: JSON.stringify(payload),
          signal: AbortSignal.timeout(10000)
        });
        results[appName] = res.ok ? 'configured' : `failed: ${await res.text()}`;
      } catch (e) {
        results[appName] = `error: ${e.message}`;
      }
    }

    return results;
  }

  // Helper: Test a service connection (reusable logic)
  async function testServiceConnection(service, url, apiKey) {
    const baseUrl = url.replace(/\/+$/, '');
    let apiEndpoint, headers;

    if (service === 'radarr' || service === 'sonarr' || service === 'lidarr') {
      apiEndpoint = `${baseUrl}/api/v3/system/status`;
      headers = { 'X-Api-Key': apiKey, 'Accept': 'application/json' };
    } else if (service === 'prowlarr') {
      apiEndpoint = `${baseUrl}/api/v1/system/status`;
      headers = { 'X-Api-Key': apiKey, 'Accept': 'application/json' };
    } else if (service === 'plex') {
      apiEndpoint = `${baseUrl}/identity`;
      headers = { 'X-Plex-Token': apiKey, 'Accept': 'application/json' };
    } else {
      return { success: false, error: `Unknown service: ${service}` };
    }

    try {
      const response = await fetchT(apiEndpoint, {
        method: 'GET',
        headers,
        signal: AbortSignal.timeout(15000)
      });

      if (response.ok) {
        const data = await response.json();
        if (service === 'plex') {
          return { success: true, version: data.MediaContainer?.version, appName: 'Plex' };
        }
        return { success: true, version: data.version, appName: data.appName };
      } else if (response.status === 401) {
        return { success: false, error: 'Invalid API key' };
      } else {
        return { success: false, error: `HTTP ${response.status}` };
      }
    } catch (e) {
      if (e.cause?.code === 'ECONNREFUSED') return { success: false, error: 'Connection refused' };
      if (e.name === 'AbortError') return { success: false, error: 'Connection timeout' };
      return { success: false, error: e.message };
    }
  }

  // Helper: Get Overseerr API key (convenience wrapper)
  async function getOverseerrApiKey() {
    const session = await getOverseerrSession();
    return session;
  }

  return {
    getArrApiKey,
    getPlexToken,
    getServiceUrl,
    getOverseerrSession,
    getOverseerrApiKey,
    connectPlexToOverseerr,
    configureProwlarrApps,
    testServiceConnection
  };
};
