import { spawn } from 'child_process';
import { promises as fs } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
import log from 'electron-log';
import { execAsync, getEnvWithPath } from '../utils/shell-utils';
import { createOutputSender } from '../utils/ipc-output-helper';
import { isNewerVersion } from '../utils/version-utils';

export interface DockerVersion {
  version: string;
  buildNumber: string;
  downloadUrl: string;
  isLatest: boolean;
}

export interface DockerSettings {
  cpus: number;
  cpusRecommended: number;
  memoryMiB: number;
  memoryRecommended: number;
  swapMiB: number;
  swapRecommended: number;
  diskSizeMiB: number;
  diskSizeRecommended: number;
}

export interface DockerStatus {
  installed: boolean;
  version: string | null;
  dockerDesktopVersion: string | null;
  updateAvailable: boolean;
  latestVersion: string | null;
  daemonRunning: boolean;
  composeInstalled: boolean;
  composeVersion: string | null;
  settings?: DockerSettings;
}

export interface InstallResult {
  success: boolean;
  message: string;
}

const COMMAND_TIMEOUT = 5000;

async function execWithTimeout(
  command: string,
  timeoutMs: number = COMMAND_TIMEOUT
): Promise<{ stdout: string; stderr: string }> {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const result = await execAsync(command, { signal: controller.signal });
    clearTimeout(timeout);
    return result;
  } catch (error: any) {
    clearTimeout(timeout);
    if (error.name === 'AbortError') {
      throw new Error(`Command timed out after ${timeoutMs}ms: ${command}`);
    }
    throw error;
  }
}

/**
 * Get system maximum resources
 */
async function getSystemMaxResources(): Promise<{ maxCpus: number; maxMemoryMiB: number; diskSizeMiB: number }> {
  let maxCpus = 10; // default
  let maxMemoryMiB = 30720; // default 30 GB
  let diskSizeMiB = 204800; // default 200 GB

  try {
    // Get number of CPUs
    const cpuResult = await execWithTimeout('sysctl -n hw.ncpu');
    maxCpus = parseInt(cpuResult.stdout.trim(), 10) || 10;
  } catch (error) {
    log.warn('Failed to get CPU count:', error);
  }

  try {
    // Get memory in bytes, convert to MiB
    const memResult = await execWithTimeout('sysctl -n hw.memsize');
    const memBytes = parseInt(memResult.stdout.trim(), 10);
    if (memBytes) {
      maxMemoryMiB = Math.floor(memBytes / (1024 * 1024));
    }
  } catch (error) {
    log.warn('Failed to get memory size:', error);
  }

  try {
    // Get total disk size using df command (returns 512-byte blocks)
    const diskResult = await execWithTimeout('df -k / | tail -1 | awk \'{print $2}\'');
    const diskKb = parseInt(diskResult.stdout.trim(), 10);
    if (diskKb) {
      diskSizeMiB = Math.floor(diskKb / 1024);
    }
  } catch (error) {
    log.warn('Failed to get disk size:', error);
  }

  return { maxCpus, maxMemoryMiB, diskSizeMiB };
}

/**
 * Read Docker Desktop settings from settings-store.json
 */
async function getDockerSettings(): Promise<DockerSettings | null> {
  const settingsPath = join(
    homedir(),
    'Library',
    'Group Containers',
    'group.com.docker',
    'settings-store.json'
  );

  try {
    const fileContent = await fs.readFile(settingsPath, 'utf-8');
    const settings = JSON.parse(fileContent);

    // Log the actual keys and values for debugging
    log.info('Docker settings keys:', Object.keys(settings));
    log.info('Docker settings values:', {
      cpus: settings.cpus,
      Cpus: settings.Cpus,
      memoryMiB: settings.memoryMiB,
      MemoryMiB: settings.MemoryMiB,
      swapMiB: settings.swapMiB,
      SwapMiB: settings.SwapMiB,
      diskSizeMiB: settings.diskSizeMiB,
      DiskSizeMiB: settings.DiskSizeMiB
    });

    // Get system max resources
    const { maxCpus, maxMemoryMiB, diskSizeMiB: systemDiskSizeMiB } = await getSystemMaxResources();

    // Extract settings - try both lowercase and capitalized keys
    const cpus = settings.cpus ?? settings.Cpus ?? maxCpus;
    const memoryMiB = settings.memoryMiB ?? settings.MemoryMiB ?? 8090; // default 7.9 GB if not set
    const swapMiB = settings.swapMiB ?? settings.SwapMiB ?? 1024; // default 1 GB if not set
    const diskSizeMiB = settings.diskSizeMiB ?? settings.DiskSizeMiB ?? systemDiskSizeMiB; // default to system disk size if not set

    log.info('Final Docker settings:', { cpus, memoryMiB, swapMiB, diskSizeMiB });

    return {
      cpus,
      cpusRecommended: 10,
      memoryMiB,
      memoryRecommended: 30720, // 30 GB
      swapMiB,
      swapRecommended: 2560, // 2.5 GB
      diskSizeMiB,
      diskSizeRecommended: 204800 // 200 GB
    };
  } catch (error: any) {
    if (error.code === 'ENOENT') {
      log.info('Docker Desktop settings file not found, using defaults');
    } else {
      log.warn('Failed to read Docker Desktop settings:', error);
    }

    // Return default values when settings file is not available
    const { maxCpus, maxMemoryMiB, diskSizeMiB: systemDiskSizeMiB } = await getSystemMaxResources();
    return {
      cpus: maxCpus,
      cpusRecommended: 10,
      memoryMiB: 8090, // default 7.9 GB
      memoryRecommended: 30720, // 30 GB
      swapMiB: 1024, // default 1 GB
      swapRecommended: 2560, // 2.5 GB
      diskSizeMiB: systemDiskSizeMiB, // default to system disk size
      diskSizeRecommended: 204800 // 200 GB
    };
  }
}

export async function checkDockerStatus(): Promise<DockerStatus> {
  const status: DockerStatus = {
    installed: false,
    version: null,
    dockerDesktopVersion: null,
    updateAvailable: false,
    latestVersion: null,
    daemonRunning: false,
    composeInstalled: false,
    composeVersion: null
  };

  try {
    // Check if Docker is installed
    await execWithTimeout('which docker');
    status.installed = true;

    // Get Docker CLI version
    try {
      const versionResult = await execWithTimeout('docker --version');
      const versionMatch = versionResult.stdout.match(/Docker version ([\d.]+)/);
      if (versionMatch) {
        status.version = versionMatch[1];
      }
    } catch (error) {
      log.warn('Failed to get Docker version:', error);
    }

    // Get Docker Desktop version from docker version output
    try {
      const versionOutput = await execWithTimeout('docker version');
      // Look for "Server: Docker Desktop X.Y.Z (build)"
      const desktopMatch = versionOutput.stdout.match(/Server:\s+Docker Desktop\s+([\d.]+)/);
      if (desktopMatch && desktopMatch[1]) {
        status.dockerDesktopVersion = desktopMatch[1];
      } else {
        log.info('Docker Desktop version not found in docker version output (may not be Docker Desktop)');
      }
    } catch (error) {
      log.info('Could not get Docker Desktop version:', error);
    }

    // Check for Docker Desktop updates via Docker's official appcast feed
    if (status.dockerDesktopVersion) {
      try {
        // Fetch the latest version from Docker's official appcast
        const appcastUrl = 'https://desktop.docker.com/mac/main/arm64/appcast.xml';
        const curlResult = await execWithTimeout(
          `curl -s "${appcastUrl}" | grep -oE 'shortVersionString="[0-9.]+"' | head -1 | cut -d'"' -f2`,
          10000
        );

        const latestVersion = curlResult.stdout.trim();

        if (latestVersion && /^\d+\.\d+\.\d+$/.test(latestVersion)) {
          status.latestVersion = latestVersion;
          log.info(`Found latest Docker version: ${latestVersion}, current: ${status.dockerDesktopVersion}`);

          // Compare versions
          if (isNewerVersion(status.dockerDesktopVersion, latestVersion)) {
            status.updateAvailable = true;
            log.info(`Docker Desktop update available: ${status.dockerDesktopVersion} -> ${latestVersion}`);
          } else {
            status.updateAvailable = false;
            log.info('Docker Desktop is up to date');
          }
        } else {
          log.warn('Could not parse version from Docker appcast');
        }
      } catch (error) {
        log.info('Could not check for Docker Desktop updates:', error);
      }
    }

    // Check if Docker daemon is running
    try {
      await execWithTimeout('docker info --format "{{.ServerVersion}}"');
      status.daemonRunning = true;
    } catch (error) {
      log.info('Docker daemon is not running');
      status.daemonRunning = false;
    }

    // Check for Docker Compose
    try {
      // Try new 'docker compose' first
      const composeResult = await execWithTimeout('docker compose version');
      const composeMatch = composeResult.stdout.match(/v?([\d.]+)/);
      if (composeMatch) {
        status.composeInstalled = true;
        status.composeVersion = composeMatch[1];
      }
    } catch (error) {
      // Fallback to legacy 'docker-compose'
      try {
        const legacyResult = await execWithTimeout('docker-compose --version');
        const legacyMatch = legacyResult.stdout.match(/version v?([\d.]+)/);
        if (legacyMatch) {
          status.composeInstalled = true;
          status.composeVersion = legacyMatch[1];
        }
      } catch (legacyError) {
        log.info('Docker Compose is not installed');
      }
    }

  } catch (error: any) {
    if (error.code === 'ENOENT') {
      log.info('Docker is not installed');
    } else {
      log.error('Error checking Docker status:', error);
    }
  }

  // Get Docker Desktop settings if installed
  if (status.installed && status.dockerDesktopVersion) {
    const dockerSettings = await getDockerSettings();
    if (dockerSettings) {
      status.settings = dockerSettings;
    }
  }

  return status;
}

/**
 * Fetch available Docker Desktop versions from Homebrew cask commit history.
 * Returns latest + up to 10 previous versions.
 */
export async function getDockerVersions(): Promise<DockerVersion[]> {
  try {
    const arch = process.arch === 'arm64' ? 'arm64' : 'amd64';
    const env = getEnvWithPath();

    // Fetch recent commits to the docker-desktop.rb cask file
    const { stdout: commitsJson } = await execAsync(
      `curl -s --max-time 15 -H "User-Agent: inst-agent" "https://api.github.com/repos/Homebrew/homebrew-cask/commits?path=Casks/d/docker-desktop.rb&per_page=25"`,
      { env }
    );

    let commits: Array<{ sha: string }>;
    try {
      commits = JSON.parse(commitsJson);
    } catch {
      log.error('Failed to parse GitHub commits response');
      return [];
    }

    if (!Array.isArray(commits) || commits.length === 0) {
      log.warn('No commits found for docker-desktop.rb cask');
      return [];
    }

    // Fetch all cask file contents in parallel
    const caskContents = await Promise.all(
      commits.slice(0, 25).map(({ sha }) =>
        execAsync(
          `curl -s --max-time 10 -H "User-Agent: inst-agent" "https://raw.githubusercontent.com/Homebrew/homebrew-cask/${sha}/Casks/d/docker-desktop.rb"`,
          { env }
        )
          .then(r => r.stdout)
          .catch(() => '')
      )
    );

    // Parse unique versions from cask file contents
    const versions: DockerVersion[] = [];
    const seen = new Set<string>();

    for (const content of caskContents) {
      if (versions.length >= 11) break; // latest + 10 previous
      const versionMatch = content.match(/\bversion\s+"([\d.]+),([\d]+)"/);
      if (!versionMatch) continue;
      const [, version, buildNumber] = versionMatch;
      if (seen.has(version)) continue;
      seen.add(version);
      versions.push({
        version,
        buildNumber,
        downloadUrl: `https://desktop.docker.com/mac/main/${arch}/${buildNumber}/Docker.dmg`,
        isLatest: versions.length === 0
      });
    }

    log.info(`Found ${versions.length} Docker versions via Homebrew cask history`);
    return versions;
  } catch (error: any) {
    log.error('Failed to fetch Docker versions:', error);
    return [];
  }
}

/**
 * Remove macOS quarantine attribute from Docker.app
 * This helps avoid "Security policy would not allow process" errors
 */
export async function removeDockerQuarantine(): Promise<boolean> {
  try {
    log.info('Checking for quarantine attribute on Docker.app...');

    // Check if quarantine attribute exists
    const checkResult = await execAsync('xattr -l /Applications/Docker.app 2>/dev/null || true');
    const hasQuarantine = checkResult.stdout.includes('com.apple.quarantine');

    if (!hasQuarantine) {
      log.info('No quarantine attribute found');
      return true;
    }

    log.info('Quarantine attribute detected, attempting removal...');

    // Try to remove without sudo first
    try {
      await execAsync('xattr -d com.apple.quarantine /Applications/Docker.app');
      log.info('Successfully removed quarantine attribute');
      return true;
    } catch (error) {
      log.warn('Could not remove quarantine without elevated privileges');
    }

    // Try with AppleScript to prompt for admin rights
    try {
      const removeQuarantineScript = `
        do shell script "xattr -d com.apple.quarantine /Applications/Docker.app" with administrator privileges
      `;
      await execAsync(`osascript -e '${removeQuarantineScript.replace(/'/g, "'\\''")}'`);
      log.info('Successfully removed quarantine attribute with admin privileges');
      return true;
    } catch (error: any) {
      if (error.message.includes('User canceled')) {
        log.warn('User cancelled authentication for quarantine removal');
      } else {
        log.warn('Failed to remove quarantine attribute:', error.message);
      }
      return false;
    }
  } catch (error: any) {
    log.warn('Error during quarantine removal:', error.message);
    return false;
  }
}

async function launchDockerDesktop(): Promise<void> {
  try {
    log.info('Launching Docker Desktop...');

    // Strategy 1: Try to remove quarantine attribute first (cleanest solution)
    await removeDockerQuarantine();

    // Strategy 2: Try AppleScript to simulate user action (best for triggering Gatekeeper properly)
    try {
      log.info('Attempting to launch Docker via AppleScript...');
      const appleScript = `
        tell application "Docker Desktop"
          activate
        end tell
      `;
      await execAsync(`osascript -e '${appleScript.replace(/'/g, "'\\''")}'`);
      log.info('Docker Desktop launched successfully via AppleScript');
      return;
    } catch (error: any) {
      log.warn('AppleScript launch failed:', error.message);
    }

    // Strategy 3: Try open command (more user-like than direct execution)
    try {
      log.info('Attempting to launch Docker via open command...');
      await execAsync('open -a /Applications/Docker.app/Contents/MacOS/Docker\\ Desktop.app');
      log.info('Docker Desktop launched successfully via open command');
      return;
    } catch (error: any) {
      log.warn('Open command launch failed:', error.message);
    }

    // Strategy 4: Try direct execution as last resort
    try {
      log.info('Attempting to launch Docker via direct execution...');
      await execAsync('/Applications/Docker.app/Contents/MacOS/Docker');
      log.info('Docker Desktop launched successfully via direct execution');
      return;
    } catch (error: any) {
      log.warn('Direct execution launch failed:', error.message);
    }

    // All strategies failed
    log.error('All Docker launch strategies failed. User may need to open Docker manually.');
    throw new Error(
      'Failed to launch Docker Desktop automatically. Please open Docker.app manually from your Applications folder to complete the setup.'
    );
  } catch (error: any) {
    log.error('Failed to launch Docker Desktop:', error);
    throw error;
  }
}
