const { SESSION_TTL, APP, PLEX, TIMEOUTS, buildMediaAuth } = require('../../src/utilities/constants');
const { createCache, CACHE_CONFIGS } = require('../../src/utilities/cache-config');

/**
 * Auth session handlers routes factory
 * @param {Object} deps - Explicit dependencies
 * @param {Object} deps.authManager - Auth manager
 * @param {Object} deps.credentialManager - Credential manager
 * @param {Function} deps.asyncHandler - Async route handler wrapper
 * @param {Function} deps.errorResponse - Error response helper
 * @param {Object} deps.log - Logger instance
 * @param {Function} deps.fetchT - Timeout-wrapped fetch
 * @returns {{ getAppSession: Function, appSessionCache: Object }}
 */
module.exports = function({ authManager: _authManager, credentialManager: _credentialManager, asyncHandler: _asyncHandler, errorResponse: _errorResponse, log, fetchT }) {
  const ctx = { fetchT };
  const appSessionCache = createCache(CACHE_CONFIGS.appSessions);

  async function getAppSession(serviceId, baseUrl, username, password) {
    const cached = appSessionCache.get(serviceId);
    if (cached && cached.exp > Date.now()) {
      if (cached.failed) return null;
      return cached.cookies;
    }

    let loginUrl, loginBody, contentType = 'application/x-www-form-urlencoded';
    const extraHeaders = {};
    let expectJsonToken = false;
    const formEncode = (s) => encodeURIComponent(s).replace(/\*/g, '%2A');

    switch (serviceId) {
      case 'torrent':
        loginUrl = `${baseUrl}api/v2/auth/login`;
        loginBody = `username=${formEncode(username)}&password=${formEncode(password)}`;
        extraHeaders['Authorization'] = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
        break;
      case 'router': {
        // Validate baseUrl is a safe hostname before using in shell command
        if (!baseUrl || typeof baseUrl !== 'string' || !/^https?:\/\/[a-zA-Z0-9]([a-zA-Z0-9.\-]{0,253}[a-zA-Z0-9])?(:\d{1,5})?(\/|$)/.test(baseUrl)) {
          log.warn('auth', 'Router auto-login rejected: invalid baseUrl', { serviceId, baseUrl: String(baseUrl).substring(0, 50) });
          appSessionCache.set(serviceId, { failed: true, exp: Date.now() + SESSION_TTL.FAILED_LOGIN });
          return null;
        }
        const routerBody = `username=${formEncode(username)}&password=${formEncode(password)}&Continue=Continue`;
        try {
          const { spawnSync } = require('child_process');
          const proc = spawnSync('wget', [
            '-q', '-S', `--post-data=${routerBody}`, '-O', '/dev/null',
            `${baseUrl}/cgi-bin/login.ha`
          ], { timeout: 5000, encoding: 'utf8' });
          const result = (proc.stderr || '').split('\n').slice(0, 2).join('\n');
          const locationMatch = result.match(/Location:\s*(.+)/);
          const location = locationMatch ? locationMatch[1].trim() : '';
          if (location && !location.includes('login')) {
            appSessionCache.set(serviceId, { cookies: '__ip_session=1', exp: Date.now() + SESSION_TTL.IP_SESSION });
            log.info('auth', 'Router auto-login successful (IP-based session)', { serviceId });
            return '__ip_session=1';
          }
          log.warn('auth', 'Router auto-login failed', { serviceId });
        } catch (e) {
          log.warn('auth', 'Router auto-login error', { serviceId, error: e.message?.substring(0, 100) });
        }
        appSessionCache.set(serviceId, { failed: true, exp: Date.now() + SESSION_TTL.FAILED_LOGIN });
        return null;
      }
      case 'sync':
        loginUrl = `${baseUrl}/rest/noauth/auth/password`;
        contentType = 'application/json';
        loginBody = JSON.stringify({ username, password });
        break;
      case 'chat':
        loginUrl = `${baseUrl}/api/v1/auths/signin`;
        contentType = 'application/json';
        loginBody = JSON.stringify({ email: username, password });
        expectJsonToken = true;
        break;
      case 'jellyfin':
      case 'emby': {
        const mediaAuth = buildMediaAuth(APP.DEVICE_IDS.SSO);
        try {
          const authResp = await fetchT(`${baseUrl}/Users/AuthenticateByName`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'X-Emby-Authorization': mediaAuth },
            body: JSON.stringify({ Username: username, Pw: password }),
          }, TIMEOUTS.HTTP_LONG);
          const authData = await authResp.json();
          if (authData.AccessToken) {
            const tokenData = {
              token: authData.AccessToken, userId: authData.User?.Id,
              serverId: authData.ServerId, serverName: authData.User?.ServerName || serviceId,
            };
            appSessionCache.set(serviceId, { cookies: `token=${authData.AccessToken}`, token: authData.AccessToken, tokenData, exp: Date.now() + SESSION_TTL.TOKEN_SESSION });
            log.info('auth', 'Auto-login successful (token + userId obtained)', { serviceId });
            return `token=${authData.AccessToken}`;
          }
          log.warn('auth', 'Auto-login failed', { serviceId, status: authResp.status });
        } catch (e) {
          log.warn('auth', 'Auto-login error', { serviceId, error: e.message });
        }
        appSessionCache.set(serviceId, { failed: true, exp: Date.now() + SESSION_TTL.FAILED_LOGIN });
        return null;
      }
      case 'plex': {
        try {
          const plexResp = await fetchT(PLEX.AUTH_URL, {
            method: 'POST',
            headers: {
              'Accept': 'application/json', 'Content-Type': 'application/json',
              'Authorization': `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`,
              'X-Plex-Client-Identifier': APP.DEVICE_IDS.SSO,
              'X-Plex-Product': APP.NAME, 'X-Plex-Version': APP.VERSION,
            },
            body: JSON.stringify({}),
          }, TIMEOUTS.HTTP_LONG);
          const plexData = await plexResp.json();
          const token = plexData?.user?.authToken;
          if (token) {
            appSessionCache.set(serviceId, { cookies: `plexToken=${token}`, token, exp: Date.now() + SESSION_TTL.TOKEN_SESSION });
            log.info('auth', 'Plex auto-login successful via plex.tv', { serviceId });
            return `plexToken=${token}`;
          }
          log.warn('auth', 'Plex auto-login failed: no token in response', { serviceId, status: plexResp.status });
        } catch (e) {
          log.warn('auth', 'Plex auto-login error', { serviceId, error: e.message });
        }
        appSessionCache.set(serviceId, { failed: true, exp: Date.now() + SESSION_TTL.FAILED_LOGIN });
        return null;
      }
      default:
        loginUrl = `${baseUrl.replace(/\/+$/, '')}/login`;
        loginBody = `username=${formEncode(username)}&password=${formEncode(password)}&rememberMe=on`;
        extraHeaders['Authorization'] = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
        break;
    }

    try {
      const resp = await fetchT(loginUrl, {
        method: 'POST',
        headers: { 'Content-Type': contentType, ...extraHeaders },
        body: loginBody, redirect: 'manual',
      }, TIMEOUTS.HTTP_LONG);

      if (expectJsonToken) {
        try {
          const data = await resp.json();
          if (data.token) {
            const cookies = `token=${data.token}`;
            appSessionCache.set(serviceId, { cookies, exp: Date.now() + SESSION_TTL.COOKIE_SESSION });
            log.info('auth', 'Auto-login successful (JWT token cached)', { serviceId });
            return cookies;
          }
        } catch (e) { /* JSON parse failed */ }
        log.warn('auth', 'Auto-login: no token in response', { serviceId, status: resp.status });
        appSessionCache.set(serviceId, { failed: true, exp: Date.now() + SESSION_TTL.FAILED_LOGIN });
        return null;
      }

      if (serviceId === 'torrent') {
        const text = await resp.text();
        if (text.trim() !== 'Ok.') {
          log.warn('auth', 'Auto-login failed', { serviceId, response: text.trim() });
          appSessionCache.set(serviceId, { failed: true, exp: Date.now() + SESSION_TTL.FAILED_LOGIN });
          return null;
        }
      }

      const setCookies = resp.headers.getSetCookie?.() || [];
      if (setCookies.length > 0) {
        const cookies = setCookies.map(c => c.split(';')[0]).join('; ');
        appSessionCache.set(serviceId, { cookies, exp: Date.now() + SESSION_TTL.COOKIE_SESSION });
        log.info('auth', 'Auto-login successful, session cached', { serviceId, cookieCount: setCookies.length });
        return cookies;
      }

      const rawCookie = resp.headers.get('set-cookie');
      if (rawCookie) {
        // headers.get('set-cookie') may return an array (Node http) or string
        const cookieStr = Array.isArray(rawCookie) ? rawCookie.join('; ') : rawCookie;
        const cookies = cookieStr.split(/,(?=[^ ])/).map(c => c.split(';')[0].trim()).join('; ');
        appSessionCache.set(serviceId, { cookies, exp: Date.now() + SESSION_TTL.COOKIE_SESSION });
        log.info('auth', 'Auto-login successful (fallback), session cached', { serviceId });
        return cookies;
      }

      log.warn('auth', 'Auto-login: no cookies in response', { serviceId, status: resp.status });
      appSessionCache.set(serviceId, { failed: true, exp: Date.now() + SESSION_TTL.FAILED_LOGIN });
    } catch (e) {
      log.warn('auth', 'Auto-login error', { serviceId, error: e.message });
      appSessionCache.set(serviceId, { failed: true, exp: Date.now() + SESSION_TTL.FAILED_LOGIN });
    }
    return null;
  }

  // Expose both the function and the cache so sso-gate can use them
  return { getAppSession, appSessionCache };
};
