"""
Python Test Executor

Executes pytest tests with live progress updates.
"""

import os
import re
import subprocess
import asyncio
from pathlib import Path
from typing import Dict

from ..config import get_running_tests
from ..services.error_handling import extract_pytest_error, classify_pytest_error, suggest_pytest_fix


async def run_python_tests(base_path: Path, service_id: str = "") -> Dict:
    """Fuehrt Python-Tests aus mit Live-Progress-Updates (Thread-basiert)"""
    running_tests = get_running_tests()

    if not base_path.exists():
        return {"passed": 0, "failed": 0, "total": 0, "output": f"Pfad existiert nicht: {base_path}", "failed_test_ids": []}

    # Versuche verschiedene pytest-Pfade
    pytest_paths = [
        "/opt/venv/bin/pytest",  # Docker venv
        "pytest",                 # System pytest
        "python -m pytest",       # Als Modul
    ]

    pytest_cmd = None
    for path in pytest_paths:
        try:
            check = subprocess.run(
                path.split() + ["--version"],
                capture_output=True,
                timeout=5,
            )
            if check.returncode == 0:
                pytest_cmd = path.split()
                break
        except:
            continue

    if not pytest_cmd:
        return {"passed": 0, "failed": 0, "total": 0, "output": "pytest nicht gefunden", "failed_test_ids": []}

    # Erst alle Test-Dateien zaehlen
    test_files = []
    test_dir = base_path if base_path.is_dir() else base_path.parent
    for f in test_dir.rglob("test_*.py"):
        test_files.append(f.name)
    total_files = len(test_files) if test_files else 1

    # Initialer Progress-Status
    running_tests[service_id] = {
        "current_file": "Starte Tests...",
        "files_done": 0,
        "files_total": total_files,
        "passed": 0,
        "failed": 0,
        "status": "running"
    }

    # Ergebnis-Container
    result_container = {
        "output_lines": [],
        "passed": 0,
        "failed": 0,
        "files_seen": set(),
        "current_file": "",
        "done": False,
        "error": None
    }

    def run_pytest_with_progress():
        """Laeuft in separatem Thread - blockiert nicht den Event Loop"""
        try:
            cwd = str(base_path.parent) if base_path.is_file() else str(base_path)

            # Unbuffered output fuer Echtzeit-Fortschritt
            env = os.environ.copy()
            env["PYTHONUNBUFFERED"] = "1"

            process = subprocess.Popen(
                pytest_cmd + ["-v", "-s", "--tb=short", str(base_path)],
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                text=True,
                cwd=cwd,
                bufsize=1,
                env=env,
            )

            for line in iter(process.stdout.readline, ''):
                if not line:
                    break

                result_container["output_lines"].append(line)
                line_stripped = line.strip()

                # Parse Test-Ergebnisse
                match = re.match(r'(\S+\.py)::(\S+)\s+(PASSED|FAILED|SKIPPED|ERROR)', line_stripped)
                if match:
                    file_path = match.group(1)
                    status = match.group(3)

                    file_name = Path(file_path).name
                    if file_name not in result_container["files_seen"]:
                        result_container["files_seen"].add(file_name)
                        result_container["current_file"] = file_name

                    if status == "PASSED":
                        result_container["passed"] += 1
                    elif status in ("FAILED", "ERROR"):
                        result_container["failed"] += 1

                    # Progress aktualisieren
                    running_tests[service_id] = {
                        "current_file": result_container["current_file"],
                        "files_done": len(result_container["files_seen"]),
                        "files_total": max(total_files, len(result_container["files_seen"])),
                        "passed": result_container["passed"],
                        "failed": result_container["failed"],
                        "status": "running"
                    }

            process.wait()
            result_container["done"] = True

        except Exception as e:
            result_container["error"] = str(e)
            result_container["done"] = True

    # Starte Test-Ausführung in separatem Thread
    loop = asyncio.get_event_loop()
    await loop.run_in_executor(None, run_pytest_with_progress)

    full_output = "".join(result_container["output_lines"])
    passed = result_container["passed"]
    failed = result_container["failed"]
    files_seen = result_container["files_seen"]

    if result_container["error"]:
        running_tests[service_id] = {
            "current_file": result_container["error"],
            "files_done": 0,
            "files_total": total_files,
            "passed": 0,
            "failed": 0,
            "status": "error"
        }
        return {"passed": 0, "failed": 0, "total": 0, "output": result_container["error"], "failed_test_ids": []}

    # Parse fehlgeschlagene Tests aus Output
    failed_test_ids = []
    for match in re.finditer(r'FAILED\s+(\S+)::(\S+)', full_output):
        file_path = match.group(1)
        test_name = match.group(2)
        error_msg = extract_pytest_error(full_output, test_name)
        failed_test_ids.append({
            "id": f"{file_path}::{test_name}",
            "name": test_name,
            "file_path": file_path,
            "error_message": error_msg or "Test fehlgeschlagen - keine Details",
            "error_type": classify_pytest_error(error_msg),
            "suggestion": suggest_pytest_fix(error_msg),
        })

    # Finaler Status
    running_tests[service_id] = {
        "current_file": "Abgeschlossen",
        "files_done": len(files_seen),
        "files_total": len(files_seen),
        "passed": passed,
        "failed": failed,
        "status": "completed"
    }

    return {
        "passed": passed,
        "failed": failed,
        "total": passed + failed,
        "output": full_output[:5000],
        "failed_test_ids": failed_test_ids,
    }
