"""Plugin provenance: the 2x2 reconciliation of install metadata.

Row-presence says "hermes installed this" (the .install-metadata.json
sidecar); ``.git``-presence cross-checks it. The reconciliation IS the
disambiguation — ``source`` alone does not distinguish a git install
from a manual copy (settled 2026-09-03, plugin-auto-update plan):

  row+git   = git install      — updatable via the recorded source
  neither   = manual drop      — not auto-updatable, listed as such
  git only  = self-cloned      — adoptable; origin URL carried ready
  row only  = provenance drift — flagged with a reinstall remedy

Pure functions over (plugins_dir, sidecar rows) — no globals, no
network, fully testable. The sidecar remains the single authority for
what *hermes* installed; this module only reads it.
"""

from __future__ import annotations

import json
import subprocess
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Optional


class ProvenanceClass(Enum):
    GIT = "git"
    MANUAL = "manual"
    SELF_CLONED = "self-cloned"
    DRIFT = "drift"


@dataclass
class Provenance:
    name: str
    klass: ProvenanceClass
    path: Path
    row: Optional[dict] = None          # the sidecar row, when present
    origin_url: Optional[str] = None    # self-cloned: the .git remote
    saved_update_url: Optional[str] = None  # the row's saved feed tag


def read_sidecar_rows(plugins_dir: Path) -> dict:
    """The install-metadata rows for this plugins dir; {} when absent
    or unreadable (manual-only installs never wrote one)."""
    sidecar = plugins_dir / ".install-metadata.json"
    if not sidecar.is_file():
        return {}
    try:
        data = json.loads(sidecar.read_text(encoding="utf-8-sig"))
    except (OSError, json.JSONDecodeError):
        return {}
    return data if isinstance(data, dict) else {}


def _git_origin_url(plugin_dir: Path) -> Optional[str]:
    """origin's URL from the installed .git — remote get-url on a real
    repo, config-file parse as the fallback (works for partial/copy
    installs where git refuses)."""
    # fast path: real git repo. Same resolver as install/update: PATH may be minimal
    # (gateway service, Windows without Git on PATH); no git → straight to the config parse.
    from hermes_cli.plugins_cmd import _resolve_git_executable

    git = _resolve_git_executable()
    if git is not None:
        try:
            proc = subprocess.run(
                [git, "-C", str(plugin_dir), "remote", "get-url", "origin"],
                capture_output=True,
                text=True, encoding="utf-8", errors="replace",
                timeout=10,
            )
            if proc.returncode == 0:
                url = (proc.stdout or "").strip()
                if url:
                    return url
        except (OSError, subprocess.TimeoutExpired):
            pass
    # fallback: parse .git/config directly (a .git dir with a config
    # file is all the adopt path needs)
    config = plugin_dir / ".git" / "config"
    if not config.is_file():
        return None
    try:
        text = config.read_text(encoding="utf-8-sig")
    except OSError:
        return None
    in_origin = False
    for line in text.splitlines():
        stripped = line.strip()
        if stripped == '[remote "origin"]':
            in_origin = True
            continue
        if stripped.startswith("["):
            in_origin = False
            continue
        if in_origin and stripped.startswith("url"):
            _, _, value = stripped.partition("=")
            url = value.strip()
            return url or None
    return None


def plugins_provenance(plugins_dir: Path) -> list[Provenance]:
    """Classify every plugin dir under plugins_dir per the 2x2."""
    rows = read_sidecar_rows(plugins_dir)
    out: list[Provenance] = []
    if not plugins_dir.is_dir():
        return out
    try:
        entries = sorted(plugins_dir.iterdir(), key=lambda p: p.name)
    except OSError:
        return out
    for plugin_dir in entries:
        if not plugin_dir.is_dir() or plugin_dir.name.startswith("."):
            continue
        has_git = (plugin_dir / ".git").exists()
        row = rows.get(plugin_dir.name)
        row = row if isinstance(row, dict) else None

        if row and has_git:
            klass = ProvenanceClass.GIT
        elif row and not has_git:
            klass = ProvenanceClass.DRIFT
        elif has_git and not row:
            klass = ProvenanceClass.SELF_CLONED
        else:
            klass = ProvenanceClass.MANUAL

        out.append(
            Provenance(
                name=plugin_dir.name,
                klass=klass,
                path=plugin_dir,
                row=row,
                origin_url=_git_origin_url(plugin_dir) if klass is ProvenanceClass.SELF_CLONED else None,
                saved_update_url=(row or {}).get("update_url") or None,
            )
        )
    return out