"""Tests for fourth-scan findings fixes (Items 1-9).

Covers: headers mutation fix, decompression limits, atomic quarantine write,
narrowed exception handling, content-length logging, case-insensitive Bearer,
scan-ignore.yaml, SSRF safe paths, worker prompt improvements.
"""

from __future__ import annotations

import inspect
import json
import os
import re
import tempfile

import pytest


# ---------------------------------------------------------------------------
# Item 1: Headers mutation fix in HTTP monkeypatch
# ---------------------------------------------------------------------------

class TestHeadersMutationFix:
    """Verify HTTP monkeypatch copies headers before modifying."""

    def test_headers_dict_is_copied(self):
        from autodebug.debug_log import _install_http_logging
        source = inspect.getsource(_install_http_logging)
        # Should use dict() to copy the headers
        assert "dict(kwargs.get(\"headers\")" in source or "dict(kwargs.get('headers')" in source

    def test_no_direct_mutation_of_caller_headers(self):
        from autodebug.debug_log import _install_http_logging
        source = inspect.getsource(_install_http_logging)
        # The logged_request function should copy before modifying
        # Find the _logged_request section
        idx = source.index("_logged_request")
        section = source[idx:idx + 500]
        assert "dict(" in section


# ---------------------------------------------------------------------------
# Item 2: Decompression size limit in transcript fetch
# ---------------------------------------------------------------------------

class TestDecompressionLimit:
    """Verify transcript decompression has a size limit."""

    def test_max_decompress_defined(self):
        from api.routers import runs
        source = inspect.getsource(runs)
        assert "_MAX_DECOMPRESS" in source

    def test_max_output_size_passed(self):
        from api.routers import runs
        source = inspect.getsource(runs)
        assert "max_output_size=" in source

    def test_stream_reader_bounded(self):
        from api.routers import runs
        source = inspect.getsource(runs)
        assert "reader.read(_MAX_DECOMPRESS)" in source


# ---------------------------------------------------------------------------
# Item 3: Atomic quarantine file write
# ---------------------------------------------------------------------------

class TestAtomicQuarantineWrite:
    """Verify quarantine file uses atomic write pattern."""

    def test_uses_tempfile(self):
        import autodebug.flaky_quarantine as fq
        source = inspect.getsource(fq)
        assert "mkstemp" in source

    def test_uses_replace(self):
        import autodebug.flaky_quarantine as fq
        source = inspect.getsource(fq)
        assert ".replace(" in source

    def test_cleans_up_on_failure(self):
        import autodebug.flaky_quarantine as fq
        source = inspect.getsource(fq)
        assert "unlink(missing_ok=True)" in source


# ---------------------------------------------------------------------------
# Item 4: Narrowed exception handling in test runner
# ---------------------------------------------------------------------------

class TestNarrowedExceptionHandling:
    """Verify batch test runner uses specific exceptions and logging."""

    def test_no_bare_except_pass(self):
        from autodebug.flaky_quarantine import _run_batch_and_parse
        source = inspect.getsource(_run_batch_and_parse)
        # Should not have bare "except Exception:\n        pass"
        assert "except Exception:\n        pass" not in source

    def test_logs_timeout(self):
        from autodebug.flaky_quarantine import _run_batch_and_parse
        source = inspect.getsource(_run_batch_and_parse)
        assert "timed out" in source.lower() or "TimeoutExpired" in source

    def test_uses_specific_exceptions(self):
        from autodebug.flaky_quarantine import _run_batch_and_parse
        source = inspect.getsource(_run_batch_and_parse)
        assert "OSError" in source or "SubprocessError" in source


# ---------------------------------------------------------------------------
# Item 5: Content-length instead of resp.content for logging
# ---------------------------------------------------------------------------

class TestContentLengthLogging:
    """Verify HTTP logging uses content-length header."""

    def test_uses_content_length_header(self):
        from autodebug.debug_log import _install_http_logging
        source = inspect.getsource(_install_http_logging)
        assert "content-length" in source

    def test_no_resp_content_for_body_len(self):
        from autodebug.debug_log import _install_http_logging
        source = inspect.getsource(_install_http_logging)
        # The success path should NOT use resp.content for body length
        # (it may still appear in error path or other contexts)
        idx = source.index("response_body_len")
        line = source[idx:idx + 80]
        assert "resp.content" not in line


# ---------------------------------------------------------------------------
# Item 6: Case-insensitive Bearer check
# ---------------------------------------------------------------------------

class TestBearerCaseInsensitive:
    """Verify Bearer auth check is case-insensitive."""

    def test_verify_token_case_insensitive(self):
        from api.auth import verify_token
        source = inspect.getsource(verify_token)
        assert ".lower()" in source
        assert 'bearer ' in source.lower()

    def test_verify_authorization_case_insensitive(self):
        from api.auth import verify_authorization
        source = inspect.getsource(verify_authorization)
        assert ".lower()" in source


# ---------------------------------------------------------------------------
# Item 7: scan-ignore.yaml exists and has correct entries
# ---------------------------------------------------------------------------

class TestScanIgnoreYaml:
    """Verify scan-ignore.yaml has entries for known false positives."""

    def test_file_exists(self):
        path = os.path.join(
            os.path.dirname(os.path.dirname(__file__)),
            ".hikaflow", "scan-ignore.yaml",
        )
        assert os.path.exists(path)

    def test_contains_semgrep_suppressions(self):
        import yaml
        path = os.path.join(
            os.path.dirname(os.path.dirname(__file__)),
            ".hikaflow", "scan-ignore.yaml",
        )
        with open(path) as f:
            data = yaml.safe_load(f)
        assert "semgrep" in data
        rules = [s["rule"] for s in data["semgrep"]]
        assert "unverified-jwt-decode" in rules
        assert "pickles-in-pytorch" in rules
        assert "tarfile-extractall-traversal" in rules

    def test_contains_review_suppressions(self):
        import yaml
        path = os.path.join(
            os.path.dirname(os.path.dirname(__file__)),
            ".hikaflow", "scan-ignore.yaml",
        )
        with open(path) as f:
            data = yaml.safe_load(f)
        assert "review" in data
        assert any("ssrf" in s.get("pattern", "") for s in data["review"])


# ---------------------------------------------------------------------------
# Item 8: SSRF safe paths filter
# ---------------------------------------------------------------------------

class TestSsrfSafePathsFilter:
    """Verify SSRF filter includes safe path matching via scan-ignore.yaml."""

    def test_ssrf_safe_paths_in_scan_ignore(self):
        import yaml
        path = os.path.join(
            os.path.dirname(os.path.dirname(__file__)),
            ".hikaflow", "scan-ignore.yaml",
        )
        with open(path) as f:
            data = yaml.safe_load(f)
        review_entries = data.get("review", [])
        ssrf_entry = next((e for e in review_entries if e.get("pattern") == "ssrf"), None)
        assert ssrf_entry is not None

    def test_safe_paths_include_db(self):
        import yaml
        path = os.path.join(
            os.path.dirname(os.path.dirname(__file__)),
            ".hikaflow", "scan-ignore.yaml",
        )
        with open(path) as f:
            data = yaml.safe_load(f)
        review_entries = data.get("review", [])
        ssrf_entry = next((e for e in review_entries if e.get("pattern") == "ssrf"), None)
        assert ssrf_entry is not None
        assert "api/db/" in ssrf_entry["paths"]


# ---------------------------------------------------------------------------
# Item 9: Worker prompt improvements
# ---------------------------------------------------------------------------

class TestWorkerPromptImprovements:
    """Verify worker prompt has new accuracy rules."""

    def test_documented_intent_rule(self):
        from autodebug.agent.system_prompt import REVIEW_WORKER_PROMPT
        assert "documented intent" in REVIEW_WORKER_PROMPT.lower() or "Trust documented intent" in REVIEW_WORKER_PROMPT

    def test_hashlib_md5_rule(self):
        # md5 guidance moved to specialized security sub-prompt
        import inspect
        from autodebug.agent import system_prompt
        full_source = inspect.getsource(system_prompt)
        assert "hashlib.md5" in full_source or "md5" in full_source.lower()
