# Copyright (c) 2026 Pyry Haulos
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT

"""Git version information utility.

This module provides a common utility for reading git repository status,
used by both the Fava server and email service dashboards to capture
version info at launch time.

Two resolution strategies are supported:

1. **Embedded** — when running from a built wheel, ``lib/_version.py``
   (generated by the ``hatch_build.py`` build hook) is imported to get the
   version, SHA and commit info baked in at build time.
2. **Git fallback** — when running from a source checkout (or if
   ``_version.py`` is absent), git commands are executed at runtime.
"""

from __future__ import annotations

import logging
import subprocess
from dataclasses import dataclass
from pathlib import Path


logger = logging.getLogger(__name__)


@dataclass
class GitVersionInfo:
    """Git version information captured at service startup.

    Attributes:
        version: Human-readable version string (e.g. "v0.7.0",
            "v0.7.0-3-gabc1234").  Empty string when no version tag
            is available.
        sha_short: Short git commit SHA (7-8 characters).
        sha_full: Full 40-character git commit SHA.
        worktree_clean: True if working tree has no uncommitted changes.
        full_status: Full git status output (HEAD info + worktree status).
    """

    version: str
    sha_short: str
    sha_full: str
    worktree_clean: bool
    full_status: str


def _try_embedded() -> GitVersionInfo | None:
    """Try to load version info from the build-time embedded module.

    Returns:
        GitVersionInfo if ``lib._version`` is importable, None otherwise.
    """
    try:
        from lib._version import GIT_SHA_FULL, GIT_SHA_SHORT, VERSION
    except ImportError:
        return None

    full_status = (
        f"=== VERSION ===\n{VERSION} ({GIT_SHA_SHORT})\n\n"
        f"Built from: {GIT_SHA_FULL}"
    )

    return GitVersionInfo(
        version=VERSION,
        sha_short=GIT_SHA_SHORT,
        sha_full=GIT_SHA_FULL,
        worktree_clean=True,
        full_status=full_status,
    )


def get_git_version_info(repo_path: Path | None = None) -> GitVersionInfo:
    """Read git repository version information.

    First attempts to load embedded version info (from a built wheel).
    If that fails, captures the current HEAD commit SHA (short and full),
    worktree clean status, and full status output suitable for displaying
    in a ``/.version`` endpoint.

    Args:
        repo_path: Path to git repository root. If None, uses parent of
            this file.

    Returns:
        GitVersionInfo with all version details.
    """
    # When no explicit repo_path is given, try embedded first.
    if repo_path is None:
        embedded = _try_embedded()
        if embedded is not None:
            return embedded

    return _get_git_version_info_live(repo_path)


def _get_git_version_info_live(
    repo_path: Path | None = None,
) -> GitVersionInfo:
    """Read version info from git commands (live fallback).

    Args:
        repo_path: Path to git repository root. If None, uses parent of
            this file.

    Returns:
        GitVersionInfo with all version details from git.
    """
    if repo_path is None:
        repo_path = Path(__file__).parent.parent

    # Get version from git describe
    try:
        result = subprocess.run(
            ["git", "describe", "--tags", "--match", "v*"],
            cwd=repo_path,
            capture_output=True,
            text=True,
            check=True,
        )
        version = result.stdout.strip()
    except subprocess.CalledProcessError:
        version = ""

    # Get short SHA
    try:
        result = subprocess.run(
            ["git", "rev-parse", "--short", "HEAD"],
            cwd=repo_path,
            capture_output=True,
            text=True,
            check=True,
        )
        sha_short = result.stdout.strip()
    except subprocess.CalledProcessError:
        sha_short = "unknown"

    # Get full SHA
    try:
        result = subprocess.run(
            ["git", "rev-parse", "HEAD"],
            cwd=repo_path,
            capture_output=True,
            text=True,
            check=True,
        )
        sha_full = result.stdout.strip()
    except subprocess.CalledProcessError:
        sha_full = "unknown"

    # Check if worktree is clean
    try:
        result = subprocess.run(
            ["git", "status", "--porcelain"],
            cwd=repo_path,
            capture_output=True,
            text=True,
            check=True,
        )
        worktree_clean = len(result.stdout.strip()) == 0
    except subprocess.CalledProcessError:
        worktree_clean = False

    # Get HEAD commit info (git show HEAD --no-patch)
    try:
        result = subprocess.run(
            ["git", "show", "HEAD", "--no-patch"],
            cwd=repo_path,
            capture_output=True,
            text=True,
            check=True,
        )
        head_info = result.stdout.strip()
    except subprocess.CalledProcessError as e:
        error_msg = e.stderr.strip() if e.stderr else str(e)
        head_info = f"Error: {error_msg}"

    # Get full status
    try:
        result = subprocess.run(
            ["git", "status"],
            cwd=repo_path,
            capture_output=True,
            text=True,
            check=True,
        )
        status_info = result.stdout.strip()
    except subprocess.CalledProcessError as e:
        error_msg = e.stderr.strip() if e.stderr else str(e)
        status_info = f"Error: {error_msg}"

    # Build full_status with optional version header
    sections = []
    if version:
        sections.append(f"=== VERSION ===\n{version} ({sha_short})")
    sections.append(f"=== HEAD COMMIT ===\n{head_info}")
    sections.append(f"=== WORKING TREE STATUS ===\n{status_info}")
    full_status = "\n\n".join(sections)

    return GitVersionInfo(
        version=version,
        sha_short=sha_short,
        sha_full=sha_full,
        worktree_clean=worktree_clean,
        full_status=full_status,
    )
