"""Test subprocess execution and pytest command building."""

from __future__ import annotations

import logging
import shlex
import subprocess
import sys
from pathlib import Path
from typing import TYPE_CHECKING

from ..runtime._codeflash_wrap_decorator import get_run_tmp_file
from ..test_discovery.models import TestType

if TYPE_CHECKING:
    from .models import TestFiles

log = logging.getLogger(__name__)


def execute_test_subprocess(
    cmd_list: list[str],
    cwd: Path,
    env: dict[str, str] | None,
    timeout: int = 600,
) -> subprocess.CompletedProcess[str]:
    """Execute a subprocess with the given command list."""
    log.debug(
        "executing test run with command: %s",
        " ".join(cmd_list),
    )
    return subprocess.run(  # noqa: S603
        cmd_list,
        cwd=cwd,
        env=env,
        timeout=timeout,
        check=False,
        text=True,
        capture_output=True,
    )


def run_behavioral_tests(  # noqa: PLR0913
    test_files: TestFiles,
    test_env: dict[str, str],
    cwd: Path,
    pytest_cmd: str = "pytest",
    timeout: int | None = None,
    enable_coverage: bool = False,  # noqa: FBT001, FBT002
) -> tuple[
    Path,
    subprocess.CompletedProcess[str],
    Path | None,
    Path | None,
]:
    """Run behavioral tests to capture return values."""
    blocklisted_plugins = [
        "benchmark",
        "codspeed",
        "xdist",
        "sugar",
    ]

    test_file_paths: list[str] = []
    for tf in test_files.test_files:
        if tf.test_type == TestType.REPLAY_TEST:
            test_file_paths.extend(
                str(tf.instrumented_behavior_file_path)
                + "::"
                + test.test_function
                for test in tf.tests_in_file
            )
        elif tf.instrumented_behavior_file_path:
            test_file_paths.append(
                str(tf.instrumented_behavior_file_path),
            )
    test_file_paths = list(set(test_file_paths))

    pytest_cmd_list = [
        sys.executable,
        "-m",
        *shlex.split(pytest_cmd),
    ]
    common_args = [
        "--capture=tee-sys",
        "-q",
        f"--rootdir={cwd}",
        "--codeflash_loops_scope=session",
        "--codeflash_min_loops=1",
        "--codeflash_max_loops=1",
        "--codeflash_seconds=10.0",
    ]
    if timeout is not None:
        common_args.append(f"--timeout={timeout}")

    result_file_path = get_run_tmp_file(
        Path("pytest_results.xml"),
    )
    result_args = [
        f"--junitxml={result_file_path.as_posix()}",
        "-o",
        "junit_logging=all",
    ]

    pytest_test_env = test_env.copy()
    pytest_test_env["PYTEST_PLUGINS"] = (
        "codeflash_python.testing._pytest_plugin"
    )

    coverage_database_file: Path | None = None
    coverage_config_file: Path | None = None

    blocklist_args = [f"-p no:{plugin}" for plugin in blocklisted_plugins]

    if enable_coverage:
        from ..analysis._coverage import (  # noqa: PLC0415
            prepare_coverage_files,
        )
        from ..verification._baseline import (  # noqa: PLC0415
            jit_disabled_env,
        )

        coverage_database_file, coverage_config_file = prepare_coverage_files()
        pytest_test_env.update(jit_disabled_env())

        coverage_cmd = [
            sys.executable,
            "-m",
            "coverage",
            "run",
            f"--rcfile={coverage_config_file.as_posix()}",
            "-m",
            *shlex.split(pytest_cmd),
        ]
        # Don't block the cov plugin when running under coverage.
        cov_blocklist = [
            f"-p no:{p}" for p in blocklisted_plugins if p != "cov"
        ]
        results = execute_test_subprocess(
            coverage_cmd
            + common_args
            + cov_blocklist
            + result_args
            + test_file_paths,
            cwd=cwd,
            env=pytest_test_env,
            timeout=600,
        )
    else:
        results = execute_test_subprocess(
            pytest_cmd_list
            + common_args
            + blocklist_args
            + result_args
            + test_file_paths,
            cwd=cwd,
            env=pytest_test_env,
            timeout=600,
        )

    return (
        result_file_path,
        results,
        coverage_database_file,
        coverage_config_file,
    )


def run_benchmarking_tests(  # noqa: PLR0913
    test_files: TestFiles,
    test_env: dict[str, str],
    cwd: Path,
    pytest_cmd: str = "pytest",
    timeout: int | None = None,
    min_loops: int = 5,
    max_loops: int = 100_000,
    target_duration_seconds: float = 10.0,
) -> tuple[Path, subprocess.CompletedProcess[str]]:
    """Run benchmarking tests to measure performance."""
    blocklisted_plugins = [
        "codspeed",
        "cov",
        "benchmark",
        "profiling",
        "xdist",
        "sugar",
    ]

    pytest_cmd_list = [
        sys.executable,
        "-m",
        *shlex.split(pytest_cmd),
    ]
    test_file_paths = list(
        {
            str(tf.benchmarking_file_path)
            for tf in test_files.test_files
            if tf.benchmarking_file_path
        }
    )

    pytest_args = [
        "--capture=tee-sys",
        "-q",
        f"--rootdir={cwd}",
        "--codeflash_loops_scope=session",
        f"--codeflash_min_loops={min_loops}",
        f"--codeflash_max_loops={max_loops}",
        f"--codeflash_seconds={target_duration_seconds}",
        "--codeflash_stability_check=true",
    ]
    if timeout is not None:
        pytest_args.append(f"--timeout={timeout}")

    result_file_path = get_run_tmp_file(
        Path("pytest_results.xml"),
    )
    result_args = [
        f"--junitxml={result_file_path.as_posix()}",
        "-o",
        "junit_logging=all",
    ]

    pytest_test_env = test_env.copy()
    pytest_test_env["PYTEST_PLUGINS"] = (
        "codeflash_python.testing._pytest_plugin"
    )
    blocklist_args = [f"-p no:{plugin}" for plugin in blocklisted_plugins]

    results = execute_test_subprocess(
        pytest_cmd_list
        + pytest_args
        + blocklist_args
        + result_args
        + test_file_paths,
        cwd=cwd,
        env=pytest_test_env,
        timeout=600,
    )
    return result_file_path, results


def run_line_profile_tests(
    test_files: TestFiles,
    test_env: dict[str, str],
    cwd: Path,
    pytest_cmd: str = "pytest",
    timeout: int | None = None,
) -> tuple[Path, subprocess.CompletedProcess[str]]:
    """Run tests with line profiling enabled."""
    blocklisted_plugins = [
        "codspeed",
        "cov",
        "benchmark",
        "profiling",
        "xdist",
        "sugar",
    ]

    pytest_cmd_list = [
        sys.executable,
        "-m",
        *shlex.split(pytest_cmd),
    ]
    test_file_paths = list(
        {
            str(tf.benchmarking_file_path)
            for tf in test_files.test_files
            if tf.benchmarking_file_path
        }
    )

    pytest_args = [
        "--capture=tee-sys",
        "-q",
        f"--rootdir={cwd}",
        "--codeflash_loops_scope=session",
        "--codeflash_min_loops=1",
        "--codeflash_max_loops=1",
        "--codeflash_seconds=10.0",
    ]
    if timeout is not None:
        pytest_args.append(f"--timeout={timeout}")

    result_file_path = get_run_tmp_file(
        Path("pytest_results.xml"),
    )
    result_args = [
        f"--junitxml={result_file_path.as_posix()}",
        "-o",
        "junit_logging=all",
    ]

    pytest_test_env = test_env.copy()
    pytest_test_env["PYTEST_PLUGINS"] = (
        "codeflash_python.testing._pytest_plugin"
    )
    pytest_test_env["LINE_PROFILE"] = "1"
    blocklist_args = [f"-p no:{plugin}" for plugin in blocklisted_plugins]

    results = execute_test_subprocess(
        pytest_cmd_list
        + pytest_args
        + blocklist_args
        + result_args
        + test_file_paths,
        cwd=cwd,
        env=pytest_test_env,
        timeout=600,
    )
    return result_file_path, results
