"""Post-phase observation.

Captures git diff stats, test results, and orchestrator outcome
after each phase completes.
"""

from __future__ import annotations

import asyncio
import json
import logging
from datetime import UTC, datetime
from pathlib import Path

logger = logging.getLogger(__name__)


async def observe_phase(
    repo_path: Path,
    orchestrator_result: object | None = None,
) -> dict:
    """Observe the repo state after a phase completes.

    Args:
        repo_path: Path to the repository.
        orchestrator_result: The OrchestratorResult from the phase execution.

    Returns:
        Observation dict with git_diff_summary, test_results, orchestrator_result.
    """
    observation: dict = {
        "timestamp": datetime.now(UTC).isoformat(),
    }

    # Git diff stats
    observation["git_diff_summary"] = await _git_diff_summary(repo_path)

    # Test suite detection and results
    observation["test_results"] = await _detect_and_run_tests(repo_path)

    # Extract structured data from OrchestratorResult
    if orchestrator_result is not None:
        observation["orchestrator_result"] = _extract_orchestrator_result(orchestrator_result)

    return observation


async def _git_diff_summary(repo_path: Path) -> dict:
    """Get git diff --stat summary."""
    try:
        proc = await asyncio.create_subprocess_exec(
            "git", "diff", "--stat", "HEAD~1",
            cwd=str(repo_path),
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=30)
        output = stdout.decode("utf-8", errors="replace").strip() if stdout else ""

        if not output:
            return {"files_changed": 0, "insertions": 0, "deletions": 0}

        # Parse the summary line: "N files changed, N insertions(+), N deletions(-)"
        lines = output.splitlines()
        summary_line = lines[-1] if lines else ""

        files_changed = 0
        insertions = 0
        deletions = 0

        for part in summary_line.split(","):
            part = part.strip()
            if "file" in part and "changed" in part:
                files_changed = int(part.split()[0])
            elif "insertion" in part:
                insertions = int(part.split()[0])
            elif "deletion" in part:
                deletions = int(part.split()[0])

        return {
            "files_changed": files_changed,
            "insertions": insertions,
            "deletions": deletions,
        }
    except Exception as e:
        logger.debug("git diff --stat failed: %s", e)
        return {"files_changed": 0, "insertions": 0, "deletions": 0, "error": str(e)}


async def _detect_and_run_tests(repo_path: Path) -> dict:
    """Detect test suite and check if tests pass."""
    result: dict = {"ran": False, "passed": False, "summary": ""}

    # Detect test runner
    test_cmd = _detect_test_command(repo_path)
    if not test_cmd:
        result["summary"] = "No test suite detected"
        return result

    try:
        proc = await asyncio.create_subprocess_exec(
            *test_cmd,
            cwd=str(repo_path),
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=300)
        output = stdout.decode("utf-8", errors="replace") if stdout else ""
        err_output = stderr.decode("utf-8", errors="replace") if stderr else ""

        result["ran"] = True
        result["passed"] = proc.returncode == 0

        # Extract summary from output (last few lines usually have counts)
        all_output = output + err_output
        lines = all_output.strip().splitlines()
        # Take last 5 non-empty lines as summary
        summary_lines = [l for l in lines[-10:] if l.strip()][-5:]
        result["summary"] = "\n".join(summary_lines)

    except TimeoutError:
        result["ran"] = True
        result["passed"] = False
        result["summary"] = "Test suite timed out after 300s"
    except Exception as e:
        result["summary"] = f"Test execution failed: {e}"

    return result


def _detect_test_command(repo_path: Path) -> list[str] | None:
    """Detect the test command for a project."""
    # Check Makefile for test target
    makefile = repo_path / "Makefile"
    if makefile.exists():
        try:
            content = makefile.read_text(encoding="utf-8")
            if "\ntest:" in content or "\ntest " in content:
                return ["make", "test"]
        except OSError:
            pass

    # Check pyproject.toml for pytest
    pyproject = repo_path / "pyproject.toml"
    if pyproject.exists():
        try:
            content = pyproject.read_text(encoding="utf-8")
            if "pytest" in content:
                # Check for .venv
                venv_pytest = repo_path / ".venv" / "bin" / "pytest"
                if venv_pytest.exists():
                    return [str(venv_pytest)]
                return ["pytest"]
        except OSError:
            pass

    # Check package.json for test script
    package_json = repo_path / "package.json"
    if package_json.exists():
        try:
            data = json.loads(package_json.read_text(encoding="utf-8"))
            if "test" in data.get("scripts", {}):
                return ["npm", "test"]
        except (OSError, json.JSONDecodeError):
            pass

    # Check Cargo.toml
    cargo = repo_path / "Cargo.toml"
    if cargo.exists():
        return ["cargo", "test"]

    return None


def _extract_orchestrator_result(result: object) -> dict:
    """Extract structured data from an OrchestratorResult."""
    data: dict = {}
    if hasattr(result, "success"):
        data["success"] = result.success
    if hasattr(result, "error"):
        data["error"] = result.error
    if hasattr(result, "state"):
        state = result.state
        if hasattr(state, "total_cost_usd"):
            data["cost_usd"] = state.total_cost_usd
        if hasattr(state, "children"):
            data["children"] = len(state.children)
            data["children_completed"] = sum(
                1 for c in state.children if c.status == "completed"
            )
            data["children_failed"] = sum(
                1 for c in state.children if c.status == "failed"
            )
    return data
