"""
djb editable-djb CLI - Install/uninstall djb in editable mode.

Why This Command Exists
-----------------------
When developing djb alongside a project that uses it,
it's convenient to have djb installed in "editable" mode so that changes to
djb source code are immediately reflected without reinstalling.

Why `uv add --editable` Instead of `uv pip install -e`
------------------------------------------------------
The key insight is that uv has TWO separate installation paths:

1. **uv pip install -e ./djb** - Installs directly into the virtual environment
   - Works immediately BUT gets overwritten by `uv sync` or `uv run`
   - uv sync reads pyproject.toml and reinstalls from PyPI, losing your edit

2. **uv add --editable ./djb** - Modifies pyproject.toml to track the source
   - Adds a [tool.uv.sources] section: `djb = { path = "djb", editable = true }`
   - Now `uv sync` and `uv run` respect the editable installation
   - The editable mode persists across all uv commands

This is a common gotcha with uv. The direct pip install seems to work but
silently breaks when you run any other uv command. This command ensures you
always use the correct, persistent approach.

Deployment Consideration
------------------------
When deploying to Heroku (or any production environment), we must NOT have
djb in editable mode - the pyproject.toml must reference the PyPI version.
The `djb deploy heroku` command handles this automatically by stashing the
editable configuration during deployment. A pre-commit hook also warns if
you try to commit with editable mode enabled.

Usage:
    djb editable-djb              # Install djb in editable mode
    djb editable-djb --status     # Check current mode
    djb editable-djb --uninstall  # Switch back to PyPI version
"""

from __future__ import annotations

import re
import tomllib
from pathlib import Path

import click

from djb.cli.logging import get_logger
from djb.cli.utils import run_cmd

logger = get_logger(__name__)


def get_djb_version_specifier(repo_root: Path) -> str | None:
    """Get the djb version specifier from pyproject.toml dependencies.

    Returns the version specifier (e.g., ">=0.2.6") or None if not found.
    """
    pyproject_path = repo_root / "pyproject.toml"
    if not pyproject_path.exists():
        return None

    content = pyproject_path.read_text()

    # Match patterns like "djb>=0.2.6" or 'djb>=0.2.6'
    match = re.search(r'["\']djb(>=[\d.]+)["\']', content)
    if match:
        return match.group(1)

    return None


def find_djb_dir(repo_root: Path | None = None) -> Path | None:
    """Find the djb directory relative to repo_root or cwd."""
    if repo_root is None:
        repo_root = Path.cwd()

    # Check if djb/ exists in the directory
    if (repo_root / "djb" / "pyproject.toml").exists():
        return repo_root / "djb"

    # Check if we're inside the djb directory
    if (repo_root / "pyproject.toml").exists():
        content = (repo_root / "pyproject.toml").read_text()
        if 'name = "djb"' in content:
            return repo_root

    return None


def _get_djb_source_config(pyproject_path: Path) -> dict | None:
    """Get the djb source configuration from pyproject.toml.

    Reads [tool.uv.sources.djb] from pyproject.toml if present.

    Returns:
        The djb source dict if configured, None otherwise.
    """
    try:
        with open(pyproject_path, "rb") as f:
            data = tomllib.load(f)
    except (tomllib.TOMLDecodeError, OSError):
        return None

    tool = data.get("tool", {})
    uv = tool.get("uv", {})
    sources = uv.get("sources", {})
    return sources.get("djb")


def is_djb_editable(repo_root: Path | None = None) -> bool:
    """Check if djb is currently installed in editable mode via pyproject.toml.

    This checks for a [tool.uv.sources] entry for djb with editable=true.
    """
    if repo_root is None:
        repo_root = Path.cwd()
    pyproject_path = repo_root / "pyproject.toml"
    if not pyproject_path.exists():
        return False

    djb_config = _get_djb_source_config(pyproject_path)
    if djb_config is None:
        return False
    return djb_config.get("editable", False)


def get_djb_source_path(repo_root: Path | None = None) -> str | None:
    """Get the path to the editable djb source if installed in editable mode."""
    if repo_root is None:
        repo_root = Path.cwd()
    pyproject_path = repo_root / "pyproject.toml"
    if not pyproject_path.exists():
        return None

    djb_config = _get_djb_source_config(pyproject_path)
    if djb_config is None or not djb_config.get("editable"):
        return None
    return djb_config.get("path")


def get_installed_djb_version(repo_root: Path | None = None) -> str | None:
    """Get the currently installed djb version."""
    if repo_root is None:
        repo_root = Path.cwd()

    result = run_cmd(["uv", "pip", "show", "djb"], cwd=repo_root, halt_on_fail=False)
    if result.returncode != 0:
        return None

    for line in result.stdout.splitlines():
        if line.startswith("Version:"):
            return line.split(":", 1)[1].strip()
    return None


def uninstall_editable_djb(repo_root: Path | None = None, quiet: bool = False) -> bool:
    """Uninstall editable djb and reinstall from PyPI.

    Args:
        repo_root: Project root directory (default: cwd)
        quiet: If True, suppress output messages

    Returns:
        True on success, False on failure
    """
    if repo_root is None:
        repo_root = Path.cwd()

    # Save the version specifier BEFORE removing (uv remove will delete it)
    version_specifier = get_djb_version_specifier(repo_root)

    result = run_cmd(
        ["uv", "remove", "djb"],
        cwd=repo_root,
        halt_on_fail=False,
        label="Removing editable djb",
        fail_msg="Failed to remove djb",
        quiet=quiet,
    )
    if result.returncode != 0:
        return False

    # Re-add with the original version specifier to preserve it
    # Use --refresh to bypass uv's resolver cache and get the latest from PyPI
    djb_spec = f"djb{version_specifier}" if version_specifier else "djb"
    result = run_cmd(
        ["uv", "add", "--refresh", djb_spec],
        cwd=repo_root,
        halt_on_fail=False,
        label="Re-adding djb from PyPI",
        fail_msg="Failed to add djb from PyPI",
        quiet=quiet,
    )
    if result.returncode != 0:
        return False

    if not quiet:
        version = get_installed_djb_version(repo_root)
        version_str = f" (v{version})" if version else ""
        logger.done(f"Switched to PyPI version of djb{version_str}")
    return True


def install_editable_djb(repo_root: Path | None = None, quiet: bool = False) -> bool:
    """Install djb in editable mode from local directory.

    Uses `uv add --editable` which modifies pyproject.toml to track the
    editable source, ensuring it persists across `uv sync` operations.

    Args:
        repo_root: Project root directory (default: cwd)
        quiet: If True, suppress output messages

    Returns:
        True on success, False on failure
    """
    if repo_root is None:
        repo_root = Path.cwd()

    djb_dir = find_djb_dir(repo_root)
    if not djb_dir:
        if not quiet:
            logger.fail(
                "Could not find djb directory. Run from a project containing djb/ "
                "or from inside the djb directory."
            )
        return False

    result = run_cmd(
        ["uv", "add", "--editable", str(djb_dir)],
        cwd=repo_root,
        halt_on_fail=False,
        label=f"Installing djb in editable mode from {djb_dir}",
        fail_msg="Failed to add editable djb",
        quiet=quiet,
    )
    if result.returncode != 0:
        return False

    if not quiet:
        version = get_installed_djb_version(repo_root)
        version_str = f" (v{version})" if version else ""
        logger.done(f"djb installed in editable mode{version_str}")
        logger.info(f"  Source: {djb_dir}")
        logger.note()
        logger.info("Note: pyproject.toml now has [tool.uv.sources] for djb.")
        logger.info("This ensures editable mode persists across `uv sync`.")
    return True


def show_status(repo_root: Path | None = None) -> None:
    """Show the current djb installation status."""
    if repo_root is None:
        repo_root = Path.cwd()

    version = get_installed_djb_version(repo_root)
    is_editable = is_djb_editable(repo_root)
    source_path = get_djb_source_path(repo_root)

    logger.info("djb installation status:")
    logger.note()

    if version:
        logger.info(f"  Version: {version}")
    else:
        logger.warning("  Not installed")
        return

    if is_editable:
        logger.done("  Mode: editable (local development)")
        if source_path:
            logger.info(f"  Source: {source_path}")
    else:
        logger.info("  Mode: PyPI (production)")

    logger.note()

    if is_editable:
        logger.info("To switch to PyPI version:")
        logger.tip("  djb editable-djb --uninstall")
    else:
        djb_dir = find_djb_dir(repo_root)
        if djb_dir:
            logger.info("To switch to editable mode:")
            logger.tip("  djb editable-djb")
        else:
            logger.info("No local djb directory found for editable installation.")


@click.command("editable-djb")
@click.option(
    "--uninstall",
    is_flag=True,
    help="Uninstall editable djb and use PyPI version instead.",
)
@click.option(
    "--status",
    is_flag=True,
    help="Show current djb installation status.",
)
def editable_djb(uninstall: bool, status: bool):
    """Install or uninstall djb in editable mode.

    Uses `uv add --editable` which modifies pyproject.toml to add a
    [tool.uv.sources] section. This ensures that `uv sync` and `uv run`
    respect the editable installation.

    IMPORTANT: Never use `uv pip install -e ./djb` directly - it will be
    overwritten by `uv sync`. Always use this command instead.

    \b
    Examples:
        djb editable-djb              # Install editable djb
        djb editable-djb --status     # Check current status
        djb editable-djb --uninstall  # Switch back to PyPI version
    """
    if status:
        show_status()
        return

    if uninstall:
        if not is_djb_editable():
            logger.info("djb is not currently in editable mode.")
            return
        if not uninstall_editable_djb():
            raise click.ClickException("Failed to uninstall editable djb")
    else:
        if is_djb_editable():
            source_path = get_djb_source_path()
            logger.info(f"djb is already installed in editable mode from {source_path}")
            logger.tip("Use --uninstall to switch back to PyPI version.")
            return
        if not install_editable_djb():
            raise click.ClickException("Failed to install editable djb")
