"""
Git Engine — Real git operations
==================================
All operations use real subprocess calls to the system git binary.
No gitpython abstraction — direct git invocations for transparency.

Operations:
  - init_repo(project_dir)              git init + initial commit
  - commit(project_dir, message)        git add -A + git commit
  - create_branch(project_dir, name)    git checkout -b
  - add_remote(project_dir, url)        git remote add origin
  - push(project_dir, remote, branch)   git push
  - log(project_dir, n)                 git log --oneline -N
  - diff_stat(project_dir)             git diff --stat HEAD
"""
from __future__ import annotations

import asyncio
import os
import platform
import shutil
import subprocess
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional


# ── Check git availability ────────────────────────────────────────────────────

def git_available() -> bool:
    return shutil.which("git") is not None


def _git(args: list[str], cwd: Path, timeout: int = 30) -> tuple[int, str, str]:
    """Run git synchronously. Returns (returncode, stdout, stderr)."""
    result = subprocess.run(
        ["git"] + args,
        cwd=str(cwd),
        capture_output=True,
        text=True,
        timeout=timeout,
        env={**os.environ, "GIT_TERMINAL_PROMPT": "0"},  # disable interactive prompts
    )
    return result.returncode, result.stdout.strip(), result.stderr.strip()


async def _git_async(args: list[str], cwd: Path, timeout: int = 30) -> tuple[int, str, str]:
    """Run git asynchronously."""
    proc = await asyncio.create_subprocess_exec(
        "git", *args,
        cwd=str(cwd),
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
        env={**os.environ, "GIT_TERMINAL_PROMPT": "0"},
    )
    try:
        out_b, err_b = await asyncio.wait_for(proc.communicate(), timeout=timeout)
    except asyncio.TimeoutError:
        proc.kill()
        return -1, "", f"git timeout after {timeout}s"
    return proc.returncode, out_b.decode("utf-8", errors="replace").strip(), err_b.decode("utf-8", errors="replace").strip()


# ── Core operations ───────────────────────────────────────────────────────────

@dataclass
class GitResult:
    success: bool
    stdout: str = ""
    stderr: str = ""
    error: Optional[str] = None


def _configure_local_identity(project_dir: Path) -> None:
    """Set local user identity so commits work even without global git config."""
    _git(["config", "user.email", "forge-ai@local"], project_dir)
    _git(["config", "user.name", "FORGE AI"], project_dir)


def init_repo(project_dir: Path, app_name: str = "app") -> GitResult:
    """
    git init + configure identity + initial commit.
    Safe to call even if .git already exists (idempotent).
    """
    if not git_available():
        return GitResult(success=False, error="git not installed")

    # Init
    rc, out, err = _git(["init", "-b", "main"], project_dir)
    if rc != 0:
        # Older git without -b flag
        _git(["init"], project_dir)
        _git(["checkout", "-b", "main"], project_dir)

    _configure_local_identity(project_dir)

    # .gitattributes for consistent line endings
    attrs = project_dir / ".gitattributes"
    if not attrs.exists():
        attrs.write_text("* text=auto\n*.sh text eol=lf\n")

    # Stage all and initial commit
    _git(["add", "-A"], project_dir)
    rc2, out2, err2 = _git(
        ["commit", "-m", f"feat: Initial FORGE generation — {app_name} v1.0\n\n[forge-ai generated]"],
        project_dir,
    )

    if rc2 != 0 and "nothing to commit" not in err2:
        return GitResult(success=False, stdout=out2, stderr=err2, error=f"Initial commit failed: {err2[:200]}")

    return GitResult(success=True, stdout=out2)


def commit(project_dir: Path, message: str, allow_empty: bool = False) -> GitResult:
    """Stage all changes and create a commit."""
    if not (project_dir / ".git").exists():
        return GitResult(success=False, error="Not a git repo")

    _git(["add", "-A"], project_dir)

    args = ["commit", "-m", message]
    if allow_empty:
        args.append("--allow-empty")

    rc, out, err = _git(args, project_dir)
    if rc != 0 and "nothing to commit" not in err:
        return GitResult(success=False, stdout=out, stderr=err, error=err[:200])

    return GitResult(success=True, stdout=out)


def create_branch(project_dir: Path, branch_name: str) -> GitResult:
    """Create and checkout a new branch."""
    rc, out, err = _git(["checkout", "-b", branch_name], project_dir)
    if rc != 0:
        return GitResult(success=False, stderr=err, error=err)
    return GitResult(success=True, stdout=out)


def add_remote(project_dir: Path, url: str, name: str = "origin") -> GitResult:
    """Add a git remote. If remote already exists, updates the URL."""
    # Check if remote exists
    rc, out, _ = _git(["remote", "get-url", name], project_dir)
    if rc == 0:
        # Update URL
        rc2, out2, err2 = _git(["remote", "set-url", name, url], project_dir)
        return GitResult(success=rc2 == 0, stdout=out2, stderr=err2)

    rc3, out3, err3 = _git(["remote", "add", name, url], project_dir)
    return GitResult(success=rc3 == 0, stdout=out3, stderr=err3)


async def push(
    project_dir: Path,
    remote: str = "origin",
    branch: str = "main",
    force: bool = False,
) -> GitResult:
    """Push to remote. Uses ssh or https depending on the remote URL."""
    args = ["push", remote, branch, "--set-upstream"]
    if force:
        args.append("--force-with-lease")

    rc, out, err = await _git_async(args, project_dir, timeout=60)

    if rc != 0:
        return GitResult(success=False, stdout=out, stderr=err, error=err[:300])
    return GitResult(success=True, stdout=out)


def log(project_dir: Path, n: int = 10) -> list[dict]:
    """Return last N commits as list of dicts."""
    if not (project_dir / ".git").exists():
        return []
    rc, out, _ = _git(
        ["log", f"-{n}", "--pretty=format:%H|%h|%s|%an|%ai"],
        project_dir,
    )
    if rc != 0 or not out:
        return []
    commits = []
    for line in out.splitlines():
        parts = line.split("|", 4)
        if len(parts) == 5:
            commits.append({
                "hash": parts[0],
                "short_hash": parts[1],
                "message": parts[2],
                "author": parts[3],
                "date": parts[4],
            })
    return commits


def diff_stat(project_dir: Path) -> str:
    """Return git diff --stat HEAD output."""
    if not (project_dir / ".git").exists():
        return ""
    rc, out, _ = _git(["diff", "--stat", "HEAD"], project_dir)
    return out if rc == 0 else ""


def status(project_dir: Path) -> str:
    """Return git status --short."""
    if not (project_dir / ".git").exists():
        return ""
    rc, out, _ = _git(["status", "--short"], project_dir)
    return out if rc == 0 else ""


def get_remote_url(project_dir: Path, remote: str = "origin") -> Optional[str]:
    rc, out, _ = _git(["remote", "get-url", remote], project_dir)
    return out if rc == 0 else None


# ── FORGE integration ─────────────────────────────────────────────────────────

def forge_init(project_dir: Path, app_name: str, session_id: str) -> GitResult:
    """
    Full git init for a new FORGE project.
    Creates: main branch, .forge/ metadata, initial commit.
    """
    result = init_repo(project_dir, app_name)

    # Write forge metadata
    forge_dir = project_dir / ".forge"
    forge_dir.mkdir(exist_ok=True)
    import json, time
    meta = {
        "session_id": session_id,
        "app_name": app_name,
        "generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
        "forge_version": "2.0.0",
    }
    (forge_dir / "meta.json").write_text(json.dumps(meta, indent=2))
    (forge_dir / ".gitkeep").touch()

    # Commit the metadata
    commit(project_dir, f"chore: Add FORGE metadata [session={session_id}]")

    return result
