import json
import logging
import os
import subprocess
import time
from typing import Any

from py_agent.infrastructure.pytest_runner import get_pytest_command

logger = logging.getLogger(__name__)


def find_test_files(project_path: str) -> list[str]:
    test_files = []
    for root, _, files in os.walk(project_path):
        for file in files:
            if (file.startswith("test_") and file.endswith(".py")) or file.endswith("_test.py"):
                test_files.append(os.path.join(root, file))
    return test_files


def get_pytest_command_with_coverage(
    project_path: str, coverage_report_path: str
) -> list[str] | None:
    command = get_pytest_command(project_path)
    if not command:
        return None
    command.extend(
        [
            "--cov=" + project_path,
            "--cov-report=json:" + coverage_report_path,
        ]
    )
    return command


def wait_for_valid_coverage_file(
    coverage_report_path: str, command_result: subprocess.CompletedProcess[str]
) -> dict[str, Any]:
    max_attempts = 5
    attempt = 0
    delay = 0.5

    while attempt < max_attempts:
        attempt += 1
        time.sleep(delay)
        try:
            if not os.path.exists(coverage_report_path):
                logger.info(f"Coverage file not yet created, attempt {attempt}")
            else:
                with open(coverage_report_path) as f:
                    content = f.read()
                if not content.strip():
                    logger.info(f"Coverage file is empty, attempt {attempt}")
                else:
                    json.loads(content)
                    logger.info("Successfully validated JSON, file is ready")
                    return {"success": True}
        except json.JSONDecodeError as e:
            logger.info(f"Invalid JSON in coverage file, attempt {attempt}: {e}")
        except Exception as e:
            logger.info(f"Error reading coverage file, attempt {attempt}: {e}")

    error_message = f"Failed to generate valid coverage JSON after {attempt} attempts"
    logger.error(error_message)

    return {
        "success": False,
        "error": error_message,
        "data": {
            "exit_code": command_result.returncode,
            "stdout": command_result.stdout,
            "stderr": command_result.stderr,
        },
    }


def run_coverage(project_path: str, coverage_report_path: str) -> dict[str, Any]:
    try:
        os.makedirs(os.path.dirname(coverage_report_path), exist_ok=True)

        pytest_command = get_pytest_command(project_path)
        if pytest_command is None:
            return {"success": False, "error": "Could not find pytest in the environment"}

        command = get_pytest_command_with_coverage(project_path, coverage_report_path)
        if not command:
            raise RuntimeError("Unable to locate pytest or pytest-cov")

        test_files = find_test_files(project_path)
        if not test_files:
            return {
                "success": False,
                "error": "No test files found in the project. Coverage requires test files to generate reports.",
            }

        logger.info(f"pytest coverage command: {' '.join(command)}")
        result = subprocess.run(
            command,
            cwd=project_path,
            capture_output=True,
            text=True,
            env=os.environ.copy(),
        )

        return wait_for_valid_coverage_file(coverage_report_path, result)

    except Exception as e:
        logger.exception("Error generating coverage report")
        return {"success": False, "error": str(e)}


def convert_coverage_to_json(coverage_db_path: str, coverage_report_path: str) -> dict[str, Any]:
    try:
        os.makedirs(os.path.dirname(coverage_report_path), exist_ok=True)

        command = get_pytest_command(os.path.dirname(coverage_db_path))
        if not command:
            return {
                "success": False,
                "error": "Missing required package: coverage",
                "data": {"missing_packages": ["coverage"]},
            }

        command = [command[0], "-m", "coverage", "json", "-o", coverage_report_path]

        result = subprocess.run(
            command,
            cwd=os.path.dirname(coverage_db_path),
            capture_output=True,
            text=True,
        )

        if os.path.exists(coverage_report_path):
            return {"success": True}
        else:
            return {
                "success": False,
                "error": "Failed to convert .coverage to JSON format",
                "data": {
                    "exit_code": result.returncode,
                    "stdout": result.stdout,
                    "stderr": result.stderr,
                },
            }

    except Exception as e:
        logger.exception("Error converting coverage to JSON")
        return {"success": False, "error": str(e)}
