import fs from 'fs';
import path from 'path';

import { logger } from '../common/observability';
import { StorageConfig } from '../types/storage.types';

export interface SSLConfig {
  /** Enable HTTPS */
  enabled?: boolean;
  /** Path to SSL certificate file */
  cert?: string;
  /** Path to SSL private key file */
  key?: string;
  /** Path to SSL certificate authority file (optional, for client certs) */
  ca?: string;
  /** Passphrase for the private key (optional) */
  passphrase?: string;
}

export interface RTCIceServerConfig {
  urls: string | string[];
  username?: string;
  credential?: string;
}

export interface WebRTCConfig {
  /** ICE servers configuration for WebRTC (STUN/TURN servers) */
  iceServers?: RTCIceServerConfig[];
  /** WebRTC connection timeout in milliseconds (default: 30000ms = 30s) */
  connectionTimeout?: number;
  /** Heartbeat interval for WebRTC connections in milliseconds (default: 30000ms = 30s) */
  heartbeatInterval?: number;
  /** Grace period before terminating session after browser disconnect (default: 90000ms = 90s).
   *  Must be long enough to survive computer sleep/wake cycles and Chrome background tab throttling.
   *  Chrome intensive throttling (after 5min background) delays JS timers to ~1min intervals, which
   *  can break socket.io's ping and cause a spurious disconnect for an idle-but-active user. */
  browserDisconnectTimeout?: number;
  /** Grace period before terminating session after node disconnect (default: 120000ms = 2min).
   *  Intentionally longer than browserDisconnectTimeout: a node WiFi blip requires
   *  socket.io ping timeout (~25-30s) + reconnection time before the node is back. */
  nodeDisconnectGracePeriod?: number;
  /** Allow session reconnection within grace period (default: true) */
  allowSessionReconnection?: boolean;
}

export interface SubscriptionConfig {
  /** Enable subscription-based usage limits (default: true) */
  enabled?: boolean;
  /** Cache TTL for subscription data in milliseconds (default: 300000ms = 5 minutes) */
  cacheTtlMs?: number;
  /** Fail open if subscription service is unreachable (default: true) */
  failOpen?: boolean;
}

export interface HubConfig {
  webdriverSessionPath: string;
  /** Session log storage configuration */
  storage?: StorageConfig;
  /** Path to store WDA IPA files (default: .cache/wda) */
  wdaStoragePath?: string;
  /** SSL/HTTPS configuration */
  ssl?: SSLConfig;
  /** WebSocket connection timeout configuration */
  websocket?: {
    /** Maximum time to wait for node availability before sending request (default: 60000ms = 60s) */
    nodeAvailabilityCheckTimeout?: number;
    /** Timeout for session creation requests (default: 120000ms = 120s) */
    sessionCreationTimeout?: number;
    /** Timeout for regular session requests like click, getText, etc. (default: 30000ms = 30s) */
    sessionRequestTimeout?: number;
    /** Delay in seconds before cleaning up sessions and devices for permanently offline nodes (default: 30s) */
    nodeCleanupDelaySeconds?: number;
    /** Interval for sending heartbeat updates from hub to database (default: 30000ms = 30s). Should be less than nodeMonitor.timeoutMs */
    heartbeatInterval?: number;
  };
  /** WebRTC configuration for manual device sessions */
  webrtc?: WebRTCConfig;
  /** Node monitor service configuration */
  nodeMonitor?: {
    /** How often to check for offline nodes (in milliseconds, default: 60000ms = 60s) */
    checkInterval?: number;
    /** How long a node can go without heartbeat before being marked offline (in milliseconds, default: 120000ms = 120s) */
    timeoutMs?: number;
  };
  /** Subscription/license configuration for usage limits */
  subscription?: SubscriptionConfig;
  /** AI Chat configuration for natural language device control */
  aiChat?: {
    /** Enable AI Chat feature (default: false) */
    enabled?: boolean;
    /** Google Gemini API key */
    geminiApiKey?: string;
    /** Gemini model ID (default: 'gemini-3.1-flash-lite-preview') */
    geminiModel?: string;
    /** Max tool calls per message to prevent infinite loops (default: 10) */
    maxToolCallsPerMessage?: number;
  };
}

export interface CLICommand {
  command: 'start' | 'migrate' | 'prepare-wda';
  options: Record<string, unknown>;
}

export interface PrepareWdaOptions {
  /** Path to the mobile provisioning file */
  mobileProvisioningFile?: string;
  /** Path to WebDriverAgent xcode project */
  wdaProjectPath?: string;
  /** Platform type: ios, tvos, or both (default: ios) */
  platform?: 'ios' | 'tvos' | 'both';
}

/**
 * Load configuration from file or return default
 */
export function loadConfig(configPath?: string): Partial<HubConfig> {
  const defaultConfigPath = path.resolve(process.cwd(), 'hub.config.json');
  const configFile = configPath || defaultConfigPath;

  let config: Partial<HubConfig> = {};

  // Try to load from file if it exists
  if (fs.existsSync(configFile)) {
    try {
      const fileContent = fs.readFileSync(configFile, 'utf-8');
      config = JSON.parse(fileContent) as HubConfig;
    } catch (error) {
      console.warn(`Warning: Failed to parse config file ${configFile}:`, error);
    }
  }

  return config;
}

/**
 * Save configuration to file
 */
export function saveConfig(config: HubConfig, configPath?: string): void {
  const defaultConfigPath = path.resolve(process.cwd(), 'hub.config.json');
  const configFile = configPath || defaultConfigPath;

  try {
    fs.writeFileSync(configFile, JSON.stringify(config, null, 2), 'utf-8');
  } catch (error) {
    console.error(`Failed to save config file ${configFile}:`, error);
  }
}

/**
 * Parse CLI arguments
 */
export function parseCLIArgs(): Partial<HubConfig> {
  const args = process.argv.slice(2);
  const config: Partial<HubConfig> = {};

  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    const nextArg = args[i + 1];

    switch (arg) {
      case '--webdriver-session-path':
      case '-w':
        if (nextArg) {
          config.webdriverSessionPath = nextArg;
          i++;
        }
        break;
      case '--storage-path':
      case '-s':
        if (nextArg) {
          // Quick way to set local storage path via CLI
          config.storage = {
            type: 'local',
            local: { basePath: nextArg },
          };
          i++;
        }
        break;
      case '--config':
      case '-c':
        if (nextArg) {
          // This will be handled separately
          i++;
        }
        break;
    }
  }

  return config;
}

/**
 * Get config file path from CLI arguments
 */
export function getConfigPathFromCLI(): string | undefined {
  const args = process.argv.slice(2);
  for (let i = 0; i < args.length; i++) {
    if ((args[i] === '--config' || args[i] === '-c') && args[i + 1]) {
      return args[i + 1];
    }
  }
  return undefined;
}

/**
 * Parse CLI command (migrate, prepare-wda, or start server)
 */
export function parseCLICommand(): CLICommand {
  const args = process.argv.slice(2);

  // Check if prepare-wda command is present
  if (args.includes('prepare-wda')) {
    return { command: 'prepare-wda', options: {} };
  }

  // Check if migrate command is present
  const isMigrate = args.includes('migrate');

  if (isMigrate) {
    return { command: 'migrate', options: {} };
  }

  return { command: 'start', options: {} };
}

/**
 * Get default storage configuration
 */
export function getDefaultStorageConfig(): StorageConfig {
  return {
    type: 'local',
    local: {
      basePath: path.resolve(process.cwd(), '.cache', 'device-farm', 'logs'),
    },
  };
}

/**
 * Merge config from file, CLI args, and environment variables
 */
export function getMergedConfig(configPath?: string): HubConfig {
  // Get config path from CLI if not provided
  const cliConfigPath = getConfigPathFromCLI();
  const finalConfigPath = configPath || cliConfigPath;

  // Load from file
  const baseConfig = finalConfigPath ? loadConfig(finalConfigPath) : loadConfig();

  // Parse CLI args (excluding config path)
  const cliConfig = parseCLIArgs();

  // Merge: file config < CLI args < environment variables
  // Default to /wd/hub if nothing is provided (router will add /session)
  const merged: HubConfig = {
    webdriverSessionPath:
      process.env.WEBDRIVER_SESSION_PATH ||
      cliConfig.webdriverSessionPath ||
      baseConfig.webdriverSessionPath ||
      '/wd/hub',
    storage:
      cliConfig.storage ||
      baseConfig.storage ||
      (process.env.SESSION_LOGS_PATH
        ? {
            type: 'local',
            local: { basePath: process.env.SESSION_LOGS_PATH },
          }
        : getDefaultStorageConfig()),
    wdaStoragePath: process.env.WDA_STORAGE_PATH || baseConfig.wdaStoragePath,
    ssl: (() => {
      // If baseConfig has SSL config, use it as base and override with env vars
      if (baseConfig.ssl) {
        return {
          enabled:
            process.env.SSL_ENABLED === 'true' ||
            baseConfig.ssl.enabled ||
            !!process.env.SSL_CERT ||
            !!process.env.SSL_KEY,
          cert: process.env.SSL_CERT || baseConfig.ssl.cert,
          key: process.env.SSL_KEY || baseConfig.ssl.key,
          ca: process.env.SSL_CA || baseConfig.ssl.ca,
          passphrase: process.env.SSL_PASSPHRASE || baseConfig.ssl.passphrase,
        };
      }
      // If no baseConfig SSL but env vars are set, create from env vars
      if (process.env.SSL_ENABLED === 'true' || process.env.SSL_CERT || process.env.SSL_KEY) {
        return {
          enabled:
            process.env.SSL_ENABLED === 'true' || !!process.env.SSL_CERT || !!process.env.SSL_KEY,
          cert: process.env.SSL_CERT,
          key: process.env.SSL_KEY,
          ca: process.env.SSL_CA,
          passphrase: process.env.SSL_PASSPHRASE,
        };
      }
      return undefined;
    })(),
    websocket: {
      nodeAvailabilityCheckTimeout: process.env.WEBSOCKET_NODE_AVAILABILITY_CHECK_TIMEOUT
        ? parseInt(process.env.WEBSOCKET_NODE_AVAILABILITY_CHECK_TIMEOUT, 10)
        : baseConfig.websocket?.nodeAvailabilityCheckTimeout || 60000, // 60 seconds (1 minute)
      sessionCreationTimeout: process.env.WEBSOCKET_SESSION_CREATION_TIMEOUT
        ? parseInt(process.env.WEBSOCKET_SESSION_CREATION_TIMEOUT, 10)
        : baseConfig.websocket?.sessionCreationTimeout || 120000, // 120 seconds
      sessionRequestTimeout: process.env.WEBSOCKET_SESSION_REQUEST_TIMEOUT
        ? parseInt(process.env.WEBSOCKET_SESSION_REQUEST_TIMEOUT, 10)
        : baseConfig.websocket?.sessionRequestTimeout || 30000, // 30 seconds
      nodeCleanupDelaySeconds: process.env.NODE_CLEANUP_DELAY_SECONDS
        ? parseInt(process.env.NODE_CLEANUP_DELAY_SECONDS, 10)
        : baseConfig.websocket?.nodeCleanupDelaySeconds || 30, // 30 seconds
      heartbeatInterval: process.env.WEBSOCKET_HEARTBEAT_INTERVAL
        ? parseInt(process.env.WEBSOCKET_HEARTBEAT_INTERVAL, 10)
        : baseConfig.websocket?.heartbeatInterval || 30000, // 30 seconds
    },
    webrtc: {
      iceServers: (() => {
        // Check environment variable first (highest priority)
        if (process.env.WEBRTC_ICE_SERVERS) {
          try {
            // Trim whitespace and remove surrounding quotes if present
            let envValue = process.env.WEBRTC_ICE_SERVERS.trim();
            // Remove surrounding single or double quotes
            if (
              (envValue.startsWith("'") && envValue.endsWith("'")) ||
              (envValue.startsWith('"') && envValue.endsWith('"'))
            ) {
              envValue = envValue.slice(1, -1);
            }
            const envServers = JSON.parse(envValue);
            if (Array.isArray(envServers) && envServers.length > 0) {
              logger.info('Using WEBRTC_ICE_SERVERS environment variable', { envServers });
              return envServers;
            }
          } catch (error) {
            console.warn(
              '⚠️  Failed to parse WEBRTC_ICE_SERVERS environment variable, using config file or defaults',
              error
            );
          }
        }
        // Use config file if available
        if (baseConfig.webrtc?.iceServers && baseConfig.webrtc.iceServers.length > 0) {
          return baseConfig.webrtc.iceServers;
        }
        // Default STUN servers
        return [
          {
            urls: 'stun:stun.l.google.com:19302', // Default public STUN server
          },
          {
            urls: 'stun:stun1.l.google.com:19302', // Additional STUN server
          },
          // Note: For hosted servers, TURN servers are required for NAT traversal
          // See WEBRTC_TURN_SETUP.md for configuration instructions
          // Example TURN configuration:
          // {
          //   urls: 'turn:your-turn-server.com:3478?transport=udp',
          //   username: 'your-username',
          //   credential: 'your-password',
          // },
        ];
      })(),
      connectionTimeout: process.env.WEBRTC_CONNECTION_TIMEOUT
        ? parseInt(process.env.WEBRTC_CONNECTION_TIMEOUT, 10)
        : baseConfig.webrtc?.connectionTimeout || 60000, // 60 seconds (increased for hosted environments)
      heartbeatInterval: process.env.WEBRTC_HEARTBEAT_INTERVAL
        ? parseInt(process.env.WEBRTC_HEARTBEAT_INTERVAL, 10)
        : baseConfig.webrtc?.heartbeatInterval || 30000, // 30 seconds
      browserDisconnectTimeout: process.env.WEBRTC_BROWSER_DISCONNECT_TIMEOUT
        ? parseInt(process.env.WEBRTC_BROWSER_DISCONNECT_TIMEOUT, 10)
        : baseConfig.webrtc?.browserDisconnectTimeout || 90000, // 90 seconds
      nodeDisconnectGracePeriod: process.env.WEBRTC_NODE_DISCONNECT_GRACE_PERIOD
        ? parseInt(process.env.WEBRTC_NODE_DISCONNECT_GRACE_PERIOD, 10)
        : baseConfig.webrtc?.nodeDisconnectGracePeriod || 120000, // 2 minutes
      allowSessionReconnection:
        process.env.WEBRTC_ALLOW_SESSION_RECONNECTION === 'false'
          ? false
          : (baseConfig.webrtc?.allowSessionReconnection ?? true),
    },
    nodeMonitor: {
      checkInterval: process.env.NODE_MONITOR_INTERVAL
        ? parseInt(process.env.NODE_MONITOR_INTERVAL, 10)
        : baseConfig.nodeMonitor?.checkInterval || 60000, // 60 seconds
      timeoutMs: process.env.NODE_TIMEOUT_MS
        ? parseInt(process.env.NODE_TIMEOUT_MS, 10)
        : baseConfig.nodeMonitor?.timeoutMs || 120000, // 120 seconds (2 minutes)
    },
    // Subscription is always enabled by default (no user config needed)
    subscription: {
      enabled: baseConfig.subscription?.enabled ?? true,
      cacheTtlMs: baseConfig.subscription?.cacheTtlMs ?? 300000, // 5 minutes
      failOpen: baseConfig.subscription?.failOpen ?? true,
    },
    aiChat: {
      enabled: process.env.AI_CHAT_ENABLED === 'true' || baseConfig.aiChat?.enabled || false,
      geminiApiKey: process.env.GEMINI_API_KEY || baseConfig.aiChat?.geminiApiKey || '',
      geminiModel:
        process.env.GEMINI_MODEL ||
        baseConfig.aiChat?.geminiModel ||
        'gemini-3.1-flash-lite-preview',
      maxToolCallsPerMessage: process.env.AI_CHAT_MAX_TOOL_CALLS
        ? parseInt(process.env.AI_CHAT_MAX_TOOL_CALLS, 10)
        : baseConfig.aiChat?.maxToolCallsPerMessage || 20,
    },
  };

  return merged;
}
