"""Conda, mamba, venv, virtualenv detection and activation.

Detection priority:
1. Local directories (ENV_SEARCH_DIRS) — check for bin/python or conda-meta/history
2. YAML files (CONDA_YAML_FILES) — parse for name: field
3. Meta files (ENV_META_FILES) — read first line as env name
"""

import json
import os
import shutil
import subprocess
from dataclasses import dataclass
from enum import Enum

from .config import CONDA_YAML_FILES, ENV_META_FILES, ENV_SEARCH_DIRS, Color
from .utils import _c, log_conda, log_warn


class EnvType(Enum):
    CONDA_PREFIX = "conda_prefix"
    CONDA_NAME = "conda_name"
    CONDA_YAML_MISSING = "conda_yaml_missing"
    VENV = "venv"


@dataclass
class DetectedEnv:
    env_type: EnvType
    path_or_name: str
    source: str
    python_version: str | None = None
    yaml_file: str | None = None


def _conda_env_exists(name: str) -> bool:
    """Check if a named conda env exists."""
    try:
        result = subprocess.run(
            ["conda", "env", "list", "--json"],
            capture_output=True,
            text=True,
            timeout=10,
        )
        if result.returncode == 0:
            data = json.loads(result.stdout)
            envs = data.get("envs", [])
            return any(os.path.basename(e) == name for e in envs)
    except (subprocess.TimeoutExpired, FileNotFoundError, json.JSONDecodeError):
        pass
    return False


def _parse_yaml_env_name(yaml_path: str) -> str | None:
    """Parse env name from a conda YAML file without requiring pyyaml."""
    try:
        with open(yaml_path) as f:
            for line in f:
                line = line.strip()
                if line.startswith("name:"):
                    name = line[5:].strip().strip("'\"")
                    if name:
                        return name
    except OSError:
        pass
    return None


def _get_python_version(env_path: str) -> str | None:
    """Try to get Python version from an env directory."""
    python_bin = os.path.join(env_path, "bin", "python")
    if os.path.exists(python_bin):
        try:
            result = subprocess.run(
                [python_bin, "--version"],
                capture_output=True,
                text=True,
                timeout=5,
            )
            if result.returncode == 0:
                return result.stdout.strip()
        except (subprocess.TimeoutExpired, FileNotFoundError, PermissionError):
            pass
    return None


def _detect_local_dirs() -> DetectedEnv | None:
    """Check ENV_SEARCH_DIRS for local conda envs or venvs."""
    cwd = os.getcwd()
    for dirname in ENV_SEARCH_DIRS:
        env_path = os.path.join(cwd, dirname)
        if not os.path.isdir(env_path):
            continue

        bin_dir = os.path.join(env_path, "bin")
        has_python = os.path.exists(os.path.join(bin_dir, "python"))
        if not has_python:
            continue

        # Check if it's a conda env (has conda-meta/)
        conda_meta = os.path.join(env_path, "conda-meta")
        if os.path.isdir(conda_meta):
            return DetectedEnv(
                env_type=EnvType.CONDA_PREFIX,
                path_or_name=env_path,
                source=f"{dirname}/",
                python_version=_get_python_version(env_path),
            )

        # Otherwise it's a venv/virtualenv (has bin/activate)
        activate = os.path.join(bin_dir, "activate")
        if os.path.exists(activate):
            return DetectedEnv(
                env_type=EnvType.VENV,
                path_or_name=env_path,
                source=f"{dirname}/",
                python_version=_get_python_version(env_path),
            )

    return None


def _detect_yaml() -> DetectedEnv | None:
    """Check CONDA_YAML_FILES for named conda envs."""
    cwd = os.getcwd()
    for yaml_file in CONDA_YAML_FILES:
        yaml_path = os.path.join(cwd, yaml_file)
        if not os.path.exists(yaml_path):
            continue

        name = _parse_yaml_env_name(yaml_path)
        if not name:
            continue

        if _conda_env_exists(name):
            return DetectedEnv(
                env_type=EnvType.CONDA_NAME,
                path_or_name=name,
                source=yaml_file,
                yaml_file=yaml_file,
            )
        else:
            return DetectedEnv(
                env_type=EnvType.CONDA_YAML_MISSING,
                path_or_name=name,
                source=yaml_file,
                yaml_file=yaml_file,
            )

    return None


def _detect_meta_files() -> DetectedEnv | None:
    """Check ENV_META_FILES for env name references."""
    cwd = os.getcwd()
    for meta_file in ENV_META_FILES:
        meta_path = os.path.join(cwd, meta_file)
        if not os.path.exists(meta_path):
            continue

        try:
            with open(meta_path) as f:
                name = f.readline().strip()
        except OSError:
            continue

        if not name:
            continue

        if _conda_env_exists(name):
            return DetectedEnv(
                env_type=EnvType.CONDA_NAME,
                path_or_name=name,
                source=meta_file,
            )

    return None


def detect_env() -> DetectedEnv | None:
    """Detect conda/venv in current directory. Returns None if nothing found.

    Also honors NX_NO_CONDA=1 for legacy compatibility.
    """
    if os.environ.get("NX_NO_CONDA") == "1":
        return None

    # Priority order
    result = _detect_local_dirs()
    if result:
        return result

    # Only try conda-based detection if conda is available
    if os.environ.get("CONDA_EXE") or shutil.which("conda"):
        result = _detect_yaml()
        if result:
            return result

        result = _detect_meta_files()
        if result:
            return result

    return None


def get_activate_command(env: DetectedEnv) -> str:
    """Generate shell command to activate the detected environment."""
    if env.env_type == EnvType.VENV:
        return f"source {env.path_or_name}/bin/activate"

    if env.env_type == EnvType.CONDA_YAML_MISSING:
        yaml_ref = env.yaml_file or "environment.yml"
        return (
            f"echo \"Environment '{env.path_or_name}' not installed. "
            f'Run: conda env create -f {yaml_ref}"'
        )

    return f"conda activate {env.path_or_name}"


def print_env_info(env: DetectedEnv | None) -> None:
    """Pretty-print environment detection results."""
    if env is None:
        log_warn("No conda/venv environment detected in this directory.")
        return

    type_label = {
        EnvType.CONDA_PREFIX: "Conda (local prefix)",
        EnvType.CONDA_NAME: "Conda (named)",
        EnvType.CONDA_YAML_MISSING: "Conda (not installed)",
        EnvType.VENV: "Venv/Virtualenv",
    }

    log_conda(f"Environment detected: {_c(Color.BOLD)}{type_label[env.env_type]}{_c(Color.NC)}")
    log_conda(f"  Path/Name: {env.path_or_name}")
    log_conda(f"  Source:    {env.source}")
    if env.python_version:
        log_conda(f"  Python:    {env.python_version}")
    if env.yaml_file:
        log_conda(f"  YAML:      {env.yaml_file}")

    if env.env_type == EnvType.CONDA_YAML_MISSING:
        log_warn(
            f"Environment '{env.path_or_name}' defined in {env.yaml_file} "
            f"but not installed. Run: conda env create -f {env.yaml_file}"
        )
