"""Tests for Tier 1-3 scan findings fixes.

Covers: mutation_testing async, flaky_threshold, torch.load safety,
debug_log hardening, flaky_quarantine defenses, scan tool accuracy.
"""

from __future__ import annotations

import asyncio
import itertools
import subprocess
import sys
from unittest.mock import MagicMock, patch

import pytest


# ---------------------------------------------------------------------------
# Tier 1, Fix 1: mutation_testing.py — subprocess.run wrapped in to_thread
# ---------------------------------------------------------------------------

class TestMutationTestingAsync:
    """Verify subprocess.run calls are offloaded to threads in async methods."""

    def test_run_uses_asyncio_to_thread(self):
        """MutmutRunner.run() should use asyncio.to_thread for subprocess."""
        import inspect
        from autodebug.quality.mutation_testing import MutmutRunner

        source = inspect.getsource(MutmutRunner.run)
        assert "asyncio.to_thread" in source
        assert "subprocess.run" in source

    def test_parse_results_uses_asyncio_to_thread(self):
        import inspect
        from autodebug.quality.mutation_testing import MutmutRunner

        source = inspect.getsource(MutmutRunner._parse_results)
        assert "asyncio.to_thread" in source

    def test_get_mutant_details_uses_asyncio_to_thread(self):
        import inspect
        from autodebug.quality.mutation_testing import MutmutRunner

        source = inspect.getsource(MutmutRunner._get_mutant_details)
        assert "asyncio.to_thread" in source

    def test_defusedxml_preferred_in_parse_text(self):
        """_parse_text_results should try defusedxml before stdlib."""
        import inspect
        from autodebug.quality.mutation_testing import MutmutRunner

        source = inspect.getsource(MutmutRunner._parse_text_results)
        assert "defusedxml" in source


# ---------------------------------------------------------------------------
# Tier 1, Fix 2: flaky_quarantine.py — flaky_threshold actually used
# ---------------------------------------------------------------------------

class TestFlakyThresholdUsed:
    """Verify flaky_threshold parameter is respected."""

    def test_threshold_filters_high_pass_rate(self):
        """A test with 80% pass rate should NOT be flaky at threshold=0.8."""
        from autodebug.flaky_quarantine import scan_for_flaky_tests

        import inspect
        source = inspect.getsource(scan_for_flaky_tests)
        # The comparison should use flaky_threshold, not hardcoded 1.0
        assert "flaky_threshold" in source
        assert "< flaky_threshold" in source

    def test_threshold_default_is_0_8(self):
        """Default flaky_threshold should be 0.8."""
        import inspect
        from autodebug.flaky_quarantine import scan_for_flaky_tests

        sig = inspect.signature(scan_for_flaky_tests)
        default = sig.parameters["flaky_threshold"].default
        assert default == 0.8


# ---------------------------------------------------------------------------
# Tier 1, Fix 3: ml/flaky_model.py — torch.load safety + empty data guard
# ---------------------------------------------------------------------------

class TestTorchLoadSafety:
    """Verify torch.load uses weights_only=True."""

    def test_weights_only_in_load_call(self):
        import inspect
        from autodebug.ml.flaky_model import FlakyCodeBERT

        source = inspect.getsource(FlakyCodeBERT.load)
        assert "weights_only=True" in source

    def test_empty_train_data_returns_error(self):
        from autodebug.ml.flaky_model import FlakyCodeBERT

        model = FlakyCodeBERT()
        result = model.fine_tune(train_data=[], val_data=[])
        assert result == {"error": "Empty training data"}


# ---------------------------------------------------------------------------
# Tier 1, Fix 4-5 + Tier 2: debug_log.py hardening
# ---------------------------------------------------------------------------

class TestDebugLogHardening:
    """Verify debug_log safety improvements."""

    def test_seq_counter_is_atomic(self):
        """_seq_counter should be itertools.count (thread-safe)."""
        from autodebug import debug_log
        assert isinstance(debug_log._seq_counter, itertools.count)

    def test_scrub_payload_has_max_depth(self):
        """_scrub_payload should stop recursing at max_depth."""
        from autodebug.debug_log import _scrub_payload

        # Build deeply nested dict
        nested: dict = {}
        current = nested
        for i in range(25):
            current["a"] = {}
            current = current["a"]
        current["secret"] = "sk-verysecretkey1234567890"

        result = _scrub_payload(nested)
        # At depth 20 it should truncate instead of continuing
        serialized = str(result)
        assert "[TRUNCATED:max_depth]" in serialized

    def test_scrub_payload_normal_depth_works(self):
        """Shallow payloads should be scrubbed normally."""
        from autodebug.debug_log import _scrub_payload

        data = {"user": {"email": "test@example.com"}}
        result = _scrub_payload(data)
        assert result["user"]["email"] == "[EMAIL]"

    def test_server_log_fetch_has_size_cap(self):
        """_fetch_server_logs should have a size cap constant."""
        from autodebug import debug_log
        assert hasattr(debug_log, "_MAX_SERVER_LOG_BYTES")
        assert debug_log._MAX_SERVER_LOG_BYTES == 10 * 1024 * 1024

    def test_fetch_server_logs_uses_streaming(self):
        """_fetch_server_logs should use stream=True for size cap."""
        import inspect
        from autodebug.debug_log import _fetch_server_logs

        source = inspect.getsource(_fetch_server_logs)
        assert "stream=True" in source
        assert "iter_content" in source
        assert "_MAX_SERVER_LOG_BYTES" in source

    def test_uninstall_http_logging_exists(self):
        """_uninstall_http_logging should be callable."""
        from autodebug.debug_log import _uninstall_http_logging
        assert callable(_uninstall_http_logging)

    def test_finalize_calls_uninstall(self):
        """finalize_debug_log should call _uninstall_http_logging."""
        import inspect
        from autodebug.debug_log import finalize_debug_log

        source = inspect.getsource(finalize_debug_log)
        assert "_uninstall_http_logging" in source

    def test_install_stores_originals(self):
        """_install_http_logging should store originals in _http_originals."""
        import inspect
        from autodebug.debug_log import _install_http_logging

        source = inspect.getsource(_install_http_logging)
        assert '_http_originals["requests.Session.request"]' in source
        assert '_http_originals["httpx.Client.request"]' in source


# ---------------------------------------------------------------------------
# Tier 2: flaky_quarantine.py — "--" separator and defusedxml
# ---------------------------------------------------------------------------

class TestFlakyQuarantineDefenses:
    """Verify subprocess calls use '--' separator."""

    def test_collect_uses_double_dash(self):
        import inspect
        from autodebug.flaky_quarantine import run_pytest_collect

        source = inspect.getsource(run_pytest_collect)
        assert '"--"' in source

    def test_single_test_uses_double_dash(self):
        import inspect
        from autodebug.flaky_quarantine import run_single_test

        source = inspect.getsource(run_single_test)
        assert '"--"' in source

    def test_batch_uses_double_dash(self):
        import inspect
        from autodebug.flaky_quarantine import _run_batch_and_parse

        source = inspect.getsource(_run_batch_and_parse)
        assert '"--"' in source

    def test_defusedxml_preferred(self):
        import inspect
        from autodebug.flaky_quarantine import _run_batch_and_parse

        source = inspect.getsource(_run_batch_and_parse)
        assert "defusedxml" in source


# ---------------------------------------------------------------------------
# Tier 3: Scan tool accuracy — excluded rules and worker prompt
# ---------------------------------------------------------------------------

class TestScanToolAccuracy:
    """Verify scan tool improvements for false positive reduction."""

    def test_async_without_await_in_fallback_config(self):
        # async-function-without-await moved from _SEMGREP_EXCLUDED_RULES constant
        # to _FALLBACK_AGENT_CONFIG; merged at scan time
        from autodebug.agent.core import _FALLBACK_AGENT_CONFIG
        excluded = _FALLBACK_AGENT_CONFIG["semgrep"]["excluded_rules"]
        assert "async-function-without-await" in excluded

    def test_fallback_config_has_async_excluded(self):
        from autodebug.agent.core import _FALLBACK_AGENT_CONFIG
        excluded = _FALLBACK_AGENT_CONFIG["semgrep"]["excluded_rules"]
        assert "async-function-without-await" in excluded

    def test_worker_prompt_has_torch_save_guidance(self):
        # torch.save guidance moved to specialized security sub-prompt
        import inspect
        from autodebug.agent import system_prompt
        full_source = inspect.getsource(system_prompt)
        assert "torch.save" in full_source

    def test_worker_prompt_has_sql_allowlist_guidance(self):
        from autodebug.agent.system_prompt import REVIEW_WORKER_PROMPT
        assert "allowlist" in REVIEW_WORKER_PROMPT

    def test_worker_prompt_has_jwt_decode_guidance(self):
        from autodebug.agent.system_prompt import REVIEW_WORKER_PROMPT
        assert "verify_signature" in REVIEW_WORKER_PROMPT

    def test_worker_prompt_has_tarfile_guidance(self):
        # tarfile guidance moved to specialized security sub-prompt
        import inspect
        from autodebug.agent import system_prompt
        full_source = inspect.getsource(system_prompt)
        assert "tarfile" in full_source

    def test_excluded_rules_filters_string_concat(self):
        """Simulate filtering: string-concat-in-loop should be removed."""
        from autodebug.agent.tools import _SEMGREP_EXCLUDED_RULES

        findings = [
            {"check_id": "python.lang.string-concat-in-loop", "path": "a.py"},
            {"check_id": "security.hardcoded-token", "path": "b.py"},
            {"check_id": "python.lang.string-concat-in-loop", "path": "c.py"},
        ]
        filtered = [
            f for f in findings
            if not any(excl in f.get("check_id", "") for excl in _SEMGREP_EXCLUDED_RULES)
        ]
        assert len(filtered) == 1
        assert filtered[0]["check_id"] == "security.hardcoded-token"
