import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import os from 'node:os';

export interface DetectedProject {
  name: string;
  pathHash: string;
  detectionSource: string;
}

function readGitRemoteName(dir: string): string | null {
  const gitConfigPath = path.join(dir, '.git', 'config');

  if (!fs.existsSync(gitConfigPath)) {
    return null;
  }

  const contents = fs.readFileSync(gitConfigPath, 'utf8');
  const match = contents.match(/url\s*=\s*(.+)/);

  if (!match) {
    return null;
  }

  const url = match[1].trim();
  const base = url.replace(/\.git$/, '').split(/[\\/]/).filter(Boolean).pop();

  return base ?? null;
}

function readJsonName(filePath: string): string | null {
  if (!fs.existsSync(filePath)) {
    return null;
  }

  try {
    const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));

    return typeof data.name === 'string' ? data.name : null;
  } catch {
    return null;
  }
}

function readTomlName(filePath: string): string | null {
  if (!fs.existsSync(filePath)) {
    return null;
  }

  const contents = fs.readFileSync(filePath, 'utf8');
  const match = contents.match(/^\s*name\s*=\s*"([^"]+)"/m);

  return match ? match[1] : null;
}

/**
 * Walk upward from `cwd` looking for a project root marker (.git, package.json, etc).
 * Falls back to `cwd` itself if none is found within a bounded number of
 * levels or before crossing the user's home directory - an unbounded walk
 * could otherwise attribute a prompt to an unrelated ancestor project.
 */
const MAX_UPWARD_LEVELS = 12;

function realpathOrSelf(dir: string): string {
  try {
    // On Windows, a path built from env vars like TEMP can use 8.3 short
    // names (e.g. "DEV-LA~1") that won't string-compare equal to the long
    // form os.homedir() returns for the same physical directory.
    return fs.realpathSync.native(dir);
  } catch {
    return dir;
  }
}

function findProjectRoot(cwd: string): string {
  let dir = cwd;
  const home = realpathOrSelf(os.homedir());

  for (let level = 0; level < MAX_UPWARD_LEVELS; level++) {
    // The home directory itself is never a valid project root, even if it
    // happens to contain a marker (e.g. a dotfiles .git repo) - treating it
    // as one would misattribute prompts from unrelated directories above it.
    if (realpathOrSelf(dir) === home) {
      return cwd;
    }

    if (
      fs.existsSync(path.join(dir, '.git')) ||
      fs.existsSync(path.join(dir, 'package.json')) ||
      fs.existsSync(path.join(dir, 'composer.json')) ||
      fs.existsSync(path.join(dir, 'Cargo.toml')) ||
      fs.existsSync(path.join(dir, 'pyproject.toml'))
    ) {
      return dir;
    }

    const parent = path.dirname(dir);

    if (parent === dir) {
      return cwd;
    }

    dir = parent;
  }

  return cwd;
}

export function detectProject(cwd: string): DetectedProject {
  const root = findProjectRoot(cwd);

  const gitName = readGitRemoteName(root);
  if (gitName) {
    return finalize(root, gitName, 'git');
  }

  const packageJsonName = readJsonName(path.join(root, 'package.json'));
  if (packageJsonName) {
    return finalize(root, packageJsonName, 'package_json');
  }

  const composerName = readJsonName(path.join(root, 'composer.json'));
  if (composerName) {
    return finalize(root, composerName, 'composer_json');
  }

  const cargoName = readTomlName(path.join(root, 'Cargo.toml'));
  if (cargoName) {
    return finalize(root, cargoName, 'cargo_toml');
  }

  const pyprojectName = readTomlName(path.join(root, 'pyproject.toml'));
  if (pyprojectName) {
    return finalize(root, pyprojectName, 'pyproject_toml');
  }

  return finalize(root, path.basename(root), 'directory');
}

function finalize(root: string, name: string, detectionSource: string): DetectedProject {
  return {
    name,
    pathHash: crypto.createHash('sha256').update(path.resolve(root)).digest('hex'),
    detectionSource,
  };
}
