import { createHash } from 'node:crypto';
import { existsSync, mkdirSync, chmodSync, cpSync, createWriteStream, readFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { pipeline } from 'node:stream/promises';
import https from 'node:https';
import { spawnSync } from 'node:child_process';

export function detectTarget(platform = process.platform, arch = process.arch) {
  if (platform === 'darwin' && arch === 'arm64') return 'darwin-arm64';
  if (platform === 'linux' && arch === 'x64') return 'linux-x64-gnu';
  if (platform === 'win32' && arch === 'x64') return 'win32-x64-msvc';
  return null;
}

export function artifactExtension(target) {
  return target.startsWith('win32-') ? 'zip' : 'tar.gz';
}

export function resolveInstallSource({ installSource, hasBundledBinary }) {
  if (installSource === 'bundled') return 'bundled';
  if (installSource === 'remote') return 'remote';
  return hasBundledBinary ? 'bundled' : 'remote';
}

function fileSha256(filePath) {
  const data = readFileSync(filePath);
  return createHash('sha256').update(data).digest('hex');
}

function downloadFile(url, destination) {
  return new Promise((resolve, reject) => {
    const request = https.get(url, (response) => {
      if ([301, 302, 307, 308].includes(response.statusCode || 0) && response.headers.location) {
        downloadFile(response.headers.location, destination).then(resolve).catch(reject);
        return;
      }

      if (response.statusCode !== 200) {
        reject(new Error(`Download failed (${response.statusCode}) for ${url}`));
        return;
      }

      const fileStream = createWriteStream(destination);
      pipeline(response, fileStream).then(resolve).catch(reject);
    });

    request.on('error', reject);
  });
}

function extractTarGz(archivePath, outputDir) {
  const tarResult = spawnSync('tar', ['-xzf', archivePath, '-C', outputDir], { stdio: 'inherit' });
  if (tarResult.status !== 0) {
    throw new Error('Failed to extract tar.gz artifact');
  }
}

function extractZip(archivePath, outputDir) {
  if (process.platform === 'win32') {
    const ps = spawnSync('powershell', [
      '-NoProfile',
      '-Command',
      `Expand-Archive -Path '${archivePath}' -DestinationPath '${outputDir}' -Force`,
    ], { stdio: 'inherit' });
    if (ps.status !== 0) throw new Error('Failed to extract zip artifact');
    return;
  }

  const unzipResult = spawnSync('unzip', ['-o', archivePath, '-d', outputDir], { stdio: 'inherit' });
  if (unzipResult.status !== 0) {
    throw new Error('Failed to extract zip artifact');
  }
}

function parseChecksums(content) {
  const map = new Map();
  for (const line of content.split('\n')) {
    const trimmed = line.trim();
    if (!trimmed) continue;
    const [hash, filename] = trimmed.split(/\s+/);
    if (hash && filename) map.set(filename, hash);
  }
  return map;
}

async function installFromRemote({ packageRoot, target, version, releaseTag, baseUrl }) {
  const ext = artifactExtension(target);
  const artifactName = `mate-v${version}-${target}.${ext}`;
  const vendorTargetDir = path.join(packageRoot, 'vendor', target);
  const binaryName = process.platform === 'win32' ? 'mate.exe' : 'mate';
  const binaryPath = path.join(vendorTargetDir, binaryName);

  mkdirSync(vendorTargetDir, { recursive: true });

  const tempRoot = path.join(tmpdir(), `mate-install-${Date.now()}`);
  mkdirSync(tempRoot, { recursive: true });
  const archivePath = path.join(tempRoot, artifactName);
  const checksumsPath = path.join(tempRoot, 'checksums.txt');
  const extractDir = path.join(tempRoot, 'extract');
  mkdirSync(extractDir, { recursive: true });

  try {
    const artifactUrl = `${baseUrl}/${releaseTag}/${artifactName}`;
    const checksumsUrl = `${baseUrl}/${releaseTag}/checksums.txt`;

    console.log(`Downloading ${artifactName}...`);
    await downloadFile(artifactUrl, archivePath);
    await downloadFile(checksumsUrl, checksumsPath);

    const checksumMap = parseChecksums(readFileSync(checksumsPath, 'utf8'));
    const expected = checksumMap.get(artifactName);
    if (!expected) {
      throw new Error(`Missing checksum entry for ${artifactName}`);
    }

    const actual = fileSha256(archivePath);
    if (actual !== expected) {
      throw new Error(`Checksum mismatch for ${artifactName}`);
    }

    if (ext === 'tar.gz') {
      extractTarGz(archivePath, extractDir);
    } else {
      extractZip(archivePath, extractDir);
    }

    const extractedRoot = path.join(extractDir, target);
    const sourceRoot = existsSync(extractedRoot) ? extractedRoot : extractDir;
    cpSync(sourceRoot, vendorTargetDir, { recursive: true, force: true });

    if (!existsSync(binaryPath)) {
      throw new Error(`Binary not found after remote install: ${binaryPath}`);
    }
  } finally {
    rmSync(tempRoot, { recursive: true, force: true });
  }

  return binaryPath;
}

export async function installBinary(options) {
  const {
    packageRoot,
    sourcePackageRoot = path.dirname(fileURLToPath(import.meta.url)),
    installSource = process.env.MATE_INSTALL_SOURCE,
    version,
    releaseTag = version ? `v${version}` : undefined,
    baseUrl = 'https://github.com/getmatedev/mate/releases/download',
  } = options;

  const target = detectTarget();
  if (!target) {
    throw new Error(`Unsupported platform: ${process.platform}-${process.arch}`);
  }

  const sourceVendorDir = path.join(sourcePackageRoot, 'vendor', target);
  const sourceBinaryName = process.platform === 'win32' ? 'mate.exe' : 'mate';
  const sourceBinaryPath = path.join(sourceVendorDir, sourceBinaryName);

  const hasBundledBinary = existsSync(sourceBinaryPath);

  const vendorTargetDir = path.join(packageRoot, 'vendor', target);
  const binaryName = process.platform === 'win32' ? 'mate.exe' : 'mate';
  const binaryPath = path.join(vendorTargetDir, binaryName);

  if (existsSync(binaryPath)) {
    return { target, binaryPath, skipped: true };
  }

  const source = resolveInstallSource({ installSource, hasBundledBinary });

  mkdirSync(path.dirname(vendorTargetDir), { recursive: true });

  if (source === 'bundled') {
    cpSync(sourceVendorDir, vendorTargetDir, { recursive: true, force: true });
  } else {
    if (!version || !releaseTag) {
      throw new Error('Remote install requires version and releaseTag');
    }
    await installFromRemote({ packageRoot, target, version, releaseTag, baseUrl });
  }

  if (!existsSync(binaryPath)) {
    throw new Error(`Binary not found after copy: ${binaryPath}`);
  }

  if (process.platform !== 'win32') {
    chmodSync(binaryPath, 0o755);
  }

  return { target, binaryPath, skipped: false };
}
