"""
Version information for MarkiTect.

Uses setuptools-scm for version derivation.  When running from a dev
checkout (git repo present), the version is resolved dynamically from
git tags so it stays correct even without ``pip install -e .``.
"""

import subprocess
from pathlib import Path
from typing import Optional

_PROJECT_ROOT = Path(__file__).parent.parent


# ── Low-level helpers ────────────────────────────────────────────────────

def _is_git_repo() -> bool:
    try:
        subprocess.check_output(
            ["git", "rev-parse", "--git-dir"],
            cwd=_PROJECT_ROOT,
            stderr=subprocess.DEVNULL,
        )
        return True
    except (subprocess.CalledProcessError, FileNotFoundError):
        return False


def _git(*args: str) -> Optional[str]:
    """Run a git command and return stripped stdout, or None on failure."""
    try:
        return subprocess.check_output(
            ["git", *args],
            cwd=_PROJECT_ROOT,
            stderr=subprocess.DEVNULL,
        ).decode().strip() or None
    except (subprocess.CalledProcessError, FileNotFoundError):
        return None


# ── Version resolution ───────────────────────────────────────────────────

def _git_version() -> Optional[str]:
    """Derive version from git via setuptools-scm (runtime, no install needed)."""
    try:
        from setuptools_scm import get_version
        return get_version(root=str(_PROJECT_ROOT))
    except Exception:
        return None


def _static_version() -> str:
    """Read the version baked into _version.py at install time."""
    try:
        from ._version import version
        return version
    except ImportError:
        return "unknown"


def _resolve_version() -> str:
    """Pick the best available version string.

    In a git checkout, setuptools-scm gives the authoritative answer
    (reflects tags even before reinstall).  Otherwise fall back to the
    static _version.py produced at install time.
    """
    if _is_git_repo():
        v = _git_version()
        if v:
            return v
    return _static_version()


__version__ = _resolve_version()


def get_version() -> str:
    """Return a clean version string (for ``markitect version``).

    On a tagged commit returns the tag (e.g. ``0.12.0``).
    Otherwise returns the base version without dev/local suffixes.
    Matches Unix convention: ``tool --version`` prints a stable string.
    """
    if _is_git_repo():
        tag = _git("describe", "--tags", "--exact-match", "HEAD")
        if tag:
            return tag.lstrip("v")
    # Strip .devN+local from scm version.
    return __version__.split(".dev")[0] if ".dev" in __version__ else __version__


# ── Release status (used by ``markitect release``) ──────────────────────

def get_release_status() -> dict:
    """Gather detailed release / development status.

    Returns a dict with:
        version, commit, branch, tag, is_dev, is_dirty,
        commits_since_tag, changed_files, upstream_diff.
    """
    info: dict = {
        "full_version": __version__,
        "is_git_repo": _is_git_repo(),
    }

    if not info["is_git_repo"]:
        info.update(
            version=__version__, commit=None, branch=None, tag=None,
            is_dev=".dev" in __version__, is_dirty=False,
            commits_since_tag=0, changed_files=[], upstream_diff=None,
        )
        return info

    info["commit"] = _git("rev-parse", "--short", "HEAD")
    info["branch"] = _git("rev-parse", "--abbrev-ref", "HEAD")
    info["tag"] = _git("describe", "--tags", "--exact-match", "HEAD")

    # Authoritative version: tag if present, otherwise scm-derived base.
    if info["tag"]:
        info["version"] = info["tag"].lstrip("v")
    else:
        info["version"] = __version__.split(".dev")[0] if ".dev" in __version__ else __version__
    info["is_dev"] = info["tag"] is None or ".dev" in __version__

    # Dirty working tree?
    info["is_dirty"] = _git("status", "--porcelain") is not None

    # Commits since last tag.
    describe = _git("describe", "--tags", "--long")
    if describe:
        # format: v0.12.0-3-gabcdef  →  3 commits since tag
        parts = describe.rsplit("-", 2)
        info["commits_since_tag"] = int(parts[1]) if len(parts) == 3 else 0
    else:
        # No tags at all — count all commits.
        count = _git("rev-list", "--count", "HEAD")
        info["commits_since_tag"] = int(count) if count else 0

    # Changed files (staged + unstaged + untracked).
    status_out = _git("status", "--porcelain")
    if status_out:
        info["changed_files"] = status_out.splitlines()
    else:
        info["changed_files"] = []

    # Upstream comparison.
    tracking = _git("rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}")
    if tracking:
        ahead = _git("rev-list", "--count", f"{tracking}..HEAD")
        behind = _git("rev-list", "--count", f"HEAD..{tracking}")
        info["upstream_diff"] = {
            "tracking": tracking,
            "ahead": int(ahead) if ahead else 0,
            "behind": int(behind) if behind else 0,
        }
    else:
        info["upstream_diff"] = None

    return info
