"""Shared test execution logic for MCP tool and REST API."""
import asyncio
import time
from pathlib import Path

import aiosqlite

from peon_mcp.db import row_to_dict
from peon_mcp.testing.junit import parse_junit_xml


async def execute_tests_for_task(
    db: aiosqlite.Connection,
    task_id: int,
    project_id: str
) -> dict:
    """Execute test commands for a task and record results.

    Runs all enabled test commands for the project in sort order. Records
    test results to test_runs and test_cases tables. Respects max_retries
    to prevent infinite test loops.

    This function contains the core test execution logic that's used by both
    the MCP tool (in server.py) and the REST API endpoint (in web.py).

    Args:
        db: Database connection
        task_id: The task ID to run tests for
        project_id: The project this task belongs to

    Returns:
        Dictionary with overall_status, results array, summary string, and needs_human_review flag
    """
    # Step 1: Look up enabled test_commands for the project
    cmd_rows = await db.execute_fetchall(
        "SELECT * FROM test_commands WHERE project_id = ? AND enabled = 1 ORDER BY sort_order ASC",
        (project_id,)
    )

    # Step 2: If no enabled commands, return early
    if not cmd_rows:
        return {
            "overall_status": "no_commands",
            "results": [],
            "summary": "No test commands configured for this project",
            "needs_human_review": False
        }

    # Step 3: Look up the task's worktree_path as cwd
    task_rows = await db.execute_fetchall(
        "SELECT worktree_path FROM tasks WHERE id = ?",
        (task_id,)
    )
    if not task_rows:
        return {
            "overall_status": "error",
            "results": [],
            "summary": f"Task {task_id} not found",
            "needs_human_review": True
        }

    worktree_path = task_rows[0]["worktree_path"]
    if not worktree_path:
        return {
            "overall_status": "error",
            "results": [],
            "summary": "Task has no worktree_path set",
            "needs_human_review": True
        }

    # Expand ~ to home directory
    worktree_path = str(Path(worktree_path).expanduser())

    # Step 4: Create .peon-reports/ dir if any command has report_path
    has_reports = any(row["report_path"] for row in cmd_rows)
    if has_reports:
        reports_dir = Path(worktree_path) / ".peon-reports"
        reports_dir.mkdir(exist_ok=True)

    # Step 5-6: Execute all commands, don't stop on first failure
    results = []
    overall_status = "pass"
    needs_human_review = False

    for cmd_row in cmd_rows:
        test_command_id = cmd_row["id"]
        command_name = cmd_row["name"]
        command = cmd_row["command"]
        timeout_seconds = cmd_row["timeout_seconds"]
        max_retries = cmd_row["max_retries"]
        report_path = cmd_row["report_path"]

        # Step 5a: Check retry count
        retry_rows = await db.execute_fetchall(
            "SELECT COUNT(*) as count FROM test_runs WHERE task_id = ? AND test_command_id = ? AND status != 'pass'",
            (task_id, test_command_id)
        )
        retry_count = retry_rows[0]["count"]

        # Step 5b: If retry count >= max_retries, skip execution
        if retry_count >= max_retries:
            results.append({
                "command_name": command_name,
                "status": "exhausted",
                "attempts_exhausted": True,
                "retry_count": retry_count,
                "max_retries": max_retries,
                "message": f"Test has failed {retry_count} times, exceeding max_retries ({max_retries})"
            })
            overall_status = "exhausted"
            needs_human_review = True
            continue

        # Step 5c: Execute command
        start_time = time.time()
        try:
            proc = await asyncio.create_subprocess_shell(
                command,
                cwd=worktree_path,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.STDOUT
            )

            try:
                stdout_bytes, _ = await asyncio.wait_for(
                    proc.communicate(),
                    timeout=timeout_seconds
                )
                exit_code = proc.returncode
                timed_out = False
            except asyncio.TimeoutError:
                proc.kill()
                await proc.wait()
                exit_code = -1
                stdout_bytes = b"Test execution timed out"
                timed_out = True
        except Exception as e:
            exit_code = -1
            stdout_bytes = str(e).encode()
            timed_out = False

        # Step 5d: Capture output and duration
        duration_seconds = time.time() - start_time
        output = stdout_bytes.decode(errors="replace")

        # Truncate to 10K chars
        if len(output) > 10000:
            output = output[:10000] + "\n... (truncated)"

        # Step 5e-5f: Parse report or determine status from exit code
        tests_passed = 0
        tests_failed = 0
        tests_skipped = 0
        test_cases = []

        if report_path and not timed_out:
            full_report_path = Path(worktree_path) / report_path
            if full_report_path.exists():
                parse_result = parse_junit_xml(full_report_path)
                if parse_result.success:
                    tests_passed = parse_result.passed
                    tests_failed = parse_result.failed + parse_result.errored
                    tests_skipped = parse_result.skipped
                    test_cases = parse_result.test_cases

        # Determine status
        if timed_out:
            status = "timeout"
        elif report_path and test_cases:
            # Use JUnit results if available
            status = "pass" if tests_failed == 0 else "fail"
        else:
            # Fall back to exit code
            status = "pass" if exit_code == 0 else "fail"

        # Step 5g: Insert test_runs row
        cursor = await db.execute(
            """INSERT INTO test_runs
               (task_id, project_id, test_command_id, command_name, command, status, exit_code, duration_seconds, output, tests_passed, tests_failed, tests_skipped)
               VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
            (task_id, project_id, test_command_id, command_name, command, status, exit_code, duration_seconds, output, tests_passed, tests_failed, tests_skipped)
        )
        test_run_id = cursor.lastrowid

        # Insert test_cases if we have them
        for tc in test_cases:
            await db.execute(
                """INSERT INTO test_cases
                   (test_run_id, name, classname, status, duration_seconds, message)
                   VALUES (?, ?, ?, ?, ?, ?)""",
                (test_run_id, tc["name"], tc["classname"], tc["status"], tc["duration_seconds"], tc["message"])
            )

        await db.commit()

        # Track overall status
        if status != "pass" and overall_status == "pass":
            overall_status = status

        # Build result entry
        result_entry = {
            "command_name": command_name,
            "status": status,
            "exit_code": exit_code,
            "duration_seconds": duration_seconds,
            "tests_passed": tests_passed,
            "tests_failed": tests_failed,
            "tests_skipped": tests_skipped,
            "attempts_exhausted": False,
            "retry_count": retry_count
        }

        # Add failed test details if available
        if test_cases:
            failed_tests = [
                {"name": tc["name"], "message": tc["message"]}
                for tc in test_cases
                if tc["status"] in ("fail", "error")
            ]
            if failed_tests:
                result_entry["failed_tests"] = failed_tests

        results.append(result_entry)

    # Step 7-8: Build summary
    total_commands = len(cmd_rows)
    passed_commands = sum(1 for r in results if r["status"] == "pass")
    failed_commands = sum(1 for r in results if r["status"] in ("fail", "error", "timeout"))
    exhausted_commands = sum(1 for r in results if r.get("attempts_exhausted", False))

    if exhausted_commands > 0:
        summary = f"{exhausted_commands}/{total_commands} test commands exhausted retries and need human review"
    elif failed_commands > 0:
        summary = f"{failed_commands}/{total_commands} test commands failed"
    else:
        summary = f"All {total_commands} test commands passed"

    return {
        "overall_status": overall_status,
        "results": results,
        "summary": summary,
        "needs_human_review": needs_human_review
    }
