"""Boris git manager - git operations for milestone tracking."""
import logging
import subprocess
import sys

from state import Milestone

# Git constants (inlined from config.py)
GIT_COMMIT_PREFIX = "feat"
GIT_PUSH_RETRIES = 1

IS_WINDOWS = sys.platform == "win32"

# Common kwargs for all git subprocess calls on Windows
_SUBPROCESS_ENCODING = {"encoding": "utf-8", "errors": "replace"}

logger = logging.getLogger("boris.git_manager")


def ensure_git_config(project_dir: str) -> bool:
    """Verify git user.name and user.email are configured. Sets defaults if missing.

    Returns True if config is ready (either already set or defaults applied).
    """
    config_ok = True
    for key, default in [("user.name", "Boris Orchestrator"), ("user.email", "boris@localhost")]:
        try:
            result = subprocess.run(
                ["git", "config", key],
                cwd=project_dir,
                capture_output=True,
                text=True,
                **_SUBPROCESS_ENCODING,
            )
            if result.returncode != 0 or not result.stdout.strip():
                logger.warning("Git config '%s' not set. Setting default: %s", key, default)
                set_result = subprocess.run(
                    ["git", "config", key, default],
                    cwd=project_dir,
                    capture_output=True,
                    text=True,
                    **_SUBPROCESS_ENCODING,
                )
                if set_result.returncode != 0:
                    logger.error("Failed to set git config '%s': %s", key, set_result.stderr)
                    config_ok = False
                else:
                    logger.info("Set git config '%s' = '%s'", key, default)
            else:
                logger.debug("Git config '%s' already set: %s", key, result.stdout.strip())
        except (subprocess.SubprocessError, FileNotFoundError) as e:
            logger.error("Error checking git config '%s': %s", key, e)
            config_ok = False
    return config_ok


def init_repo(project_dir: str) -> bool:
    """Initialize git repo if not already one. Returns success."""
    try:
        # Check if already a git repo
        result = subprocess.run(
            ["git", "rev-parse", "--is-inside-work-tree"],
            cwd=project_dir,
            capture_output=True,
            text=True,
            **_SUBPROCESS_ENCODING,
        )
        if result.returncode == 0:
            logger.info("Git repo already initialized in %s", project_dir)
            return True

        # Initialize new repo
        result = subprocess.run(
            ["git", "init"],
            cwd=project_dir,
            capture_output=True,
            text=True,
            **_SUBPROCESS_ENCODING,
        )
        if result.returncode == 0:
            logger.info("Initialized git repo in %s", project_dir)
            return True
        else:
            logger.warning("Failed to init git repo: %s", result.stderr)
            return False

    except (subprocess.SubprocessError, FileNotFoundError) as e:
        logger.warning("Git init error: %s", e)
        return False


def commit_milestone(project_dir: str, milestone: Milestone) -> bool:
    """Stage all changes and commit with milestone message. Returns success."""
    try:
        # git add -A
        add_result = subprocess.run(
            ["git", "add", "-A"],
            cwd=project_dir,
            capture_output=True,
            text=True,
            **_SUBPROCESS_ENCODING,
        )
        if add_result.returncode != 0:
            logger.warning("git add failed: %s", add_result.stderr)
            return False

        # git commit
        message = f"{GIT_COMMIT_PREFIX}(milestone-{milestone.id}): {milestone.title}"
        commit_result = subprocess.run(
            ["git", "commit", "-m", message],
            cwd=project_dir,
            capture_output=True,
            text=True,
            **_SUBPROCESS_ENCODING,
        )
        if commit_result.returncode != 0:
            # Could be "nothing to commit"
            if "nothing to commit" in commit_result.stdout:
                logger.info("Nothing to commit for milestone %s", milestone.id)
                return True
            logger.warning("git commit failed: %s", commit_result.stderr)
            return False

        logger.info("Committed milestone %s: %s", milestone.id, milestone.title)
        return True

    except (subprocess.SubprocessError, FileNotFoundError) as e:
        logger.warning("Git commit error: %s", e)
        return False


def push(project_dir: str, remote: str = "origin") -> bool:
    """Push to remote with retry logic. Returns success."""
    if not has_remote(project_dir):
        logger.info("No remote configured, skipping push")
        return False

    for attempt in range(GIT_PUSH_RETRIES + 1):
        try:
            result = subprocess.run(
                ["git", "push", remote],
                cwd=project_dir,
                capture_output=True,
                text=True,
                timeout=60,
                **_SUBPROCESS_ENCODING,
            )
            if result.returncode == 0:
                logger.info("Pushed to %s", remote)
                return True
            else:
                logger.warning(
                    "Push attempt %d failed: %s", attempt + 1, result.stderr
                )
        except (subprocess.SubprocessError, subprocess.TimeoutExpired) as e:
            logger.warning("Push attempt %d error: %s", attempt + 1, e)

    logger.warning("All push attempts failed")
    return False


def setup_remote(project_dir: str, remote_url: str) -> bool:
    """Set up or update the origin remote. Returns success."""
    try:
        # Check if origin already exists
        result = subprocess.run(
            ["git", "remote", "get-url", "origin"],
            cwd=project_dir,
            capture_output=True,
            text=True,
            **_SUBPROCESS_ENCODING,
        )

        if result.returncode == 0:
            # Origin exists, update URL
            set_result = subprocess.run(
                ["git", "remote", "set-url", "origin", remote_url],
                cwd=project_dir,
                capture_output=True,
                text=True,
                **_SUBPROCESS_ENCODING,
            )
        else:
            # Origin doesn't exist, add it
            set_result = subprocess.run(
                ["git", "remote", "add", "origin", remote_url],
                cwd=project_dir,
                capture_output=True,
                text=True,
                **_SUBPROCESS_ENCODING,
            )

        if set_result.returncode == 0:
            logger.info("Remote origin set to %s", remote_url)
            return True
        else:
            logger.warning("Failed to set remote: %s", set_result.stderr)
            return False

    except (subprocess.SubprocessError, FileNotFoundError) as e:
        logger.warning("Git remote setup error: %s", e)
        return False


def has_remote(project_dir: str) -> bool:
    """Check if a remote is configured."""
    try:
        result = subprocess.run(
            ["git", "remote"],
            cwd=project_dir,
            capture_output=True,
            text=True,
            **_SUBPROCESS_ENCODING,
        )
        return result.returncode == 0 and result.stdout.strip() != ""
    except (subprocess.SubprocessError, FileNotFoundError):
        return False


def final_push(project_dir: str, remote: str = "origin") -> bool:
    """Commit any remaining changes and push. Returns success."""
    try:
        # Stage any remaining changes
        subprocess.run(
            ["git", "add", "-A"],
            cwd=project_dir,
            capture_output=True,
            text=True,
            **_SUBPROCESS_ENCODING,
        )

        # Commit if there are changes
        commit_result = subprocess.run(
            ["git", "commit", "-m", "chore: Boris orchestration complete"],
            cwd=project_dir,
            capture_output=True,
            text=True,
            **_SUBPROCESS_ENCODING,
        )
        if commit_result.returncode != 0 and "nothing to commit" not in commit_result.stdout:
            logger.warning("Final commit failed: %s", commit_result.stderr)

        # Push
        return push(project_dir, remote)

    except (subprocess.SubprocessError, FileNotFoundError) as e:
        logger.warning("Final push error: %s", e)
        return False
