"""Tests for fifth-scan findings fixes (S1-S8, B1-B3, A1-A9, R3-R4, P1).

Covers: access control on synthetic endpoint, redirect_uri validation,
DASHBOARD_URL validation, postMessage origin, CLI callback scheme check,
billing HTTPS enforcement, diff content verification, GitHubConnect origin,
async blocking fixes, scan accuracy improvements, REPL quality, Redis close.
"""

from __future__ import annotations

import inspect
import os
import re

import pytest


# ---------------------------------------------------------------------------
# S1: Missing access check on /synthetic/generate
# ---------------------------------------------------------------------------

class TestSyntheticAccessCheck:
    """Verify generate_synthetic_data checks run access."""

    def test_calls_check_run_access(self):
        from api.routers import runs
        source = inspect.getsource(runs.generate_synthetic_data)
        assert "check_run_access" in source

    def test_access_check_before_storage(self):
        from api.routers import runs
        source = inspect.getsource(runs.generate_synthetic_data)
        idx_access = source.index("check_run_access")
        idx_storage = source.index("storage.get_artifact")
        assert idx_access < idx_storage, "check_run_access must come before storage.get_artifact"


# ---------------------------------------------------------------------------
# S2: PKCE redirect_uri validation
# ---------------------------------------------------------------------------

class TestPkceRedirectValidation:
    """Verify redirect_uri is validated for scheme and host."""

    def test_redirect_uri_validated(self):
        from api import cli_auth
        source = inspect.getsource(cli_auth.pkce_authorize)
        assert "redirect_uri" in source
        assert "127.0.0.1" in source or "localhost" in source

    def test_rejects_non_http_scheme(self):
        from api import cli_auth
        source = inspect.getsource(cli_auth.pkce_authorize)
        assert 'scheme != "http"' in source or "scheme != 'http'" in source or "protocol" in source


# ---------------------------------------------------------------------------
# S3: DASHBOARD_URL validation
# ---------------------------------------------------------------------------

class TestDashboardUrlValidation:
    """Verify DASHBOARD_URL is validated."""

    def test_validate_helper_exists(self):
        from api import cli_auth
        assert hasattr(cli_auth, "_validate_dashboard_url")

    def test_validates_scheme(self):
        from api.cli_auth import _validate_dashboard_url
        source = inspect.getsource(_validate_dashboard_url)
        assert "scheme" in source

    def test_used_in_device_authorize(self):
        from api import cli_auth
        source = inspect.getsource(cli_auth.device_authorize)
        assert "_validate_dashboard_url" in source

    def test_used_in_pkce_authorize(self):
        from api import cli_auth
        source = inspect.getsource(cli_auth.pkce_authorize)
        assert "_validate_dashboard_url" in source


# ---------------------------------------------------------------------------
# S4: postMessage uses specific origin
# ---------------------------------------------------------------------------

class TestPostMessageOrigin:
    """Verify postMessage uses window.location.origin, not '*'."""

    def test_no_wildcard_postmessage(self):
        path = os.path.join(
            os.path.dirname(os.path.dirname(__file__)),
            "web", "src", "app", "github", "callback", "page.tsx",
        )
        with open(path) as f:
            content = f.read()
        # Should not have postMessage(..., "*")
        assert 'postMessage(' in content
        assert ', "*")' not in content

    def test_uses_location_origin(self):
        path = os.path.join(
            os.path.dirname(os.path.dirname(__file__)),
            "web", "src", "app", "github", "callback", "page.tsx",
        )
        with open(path) as f:
            content = f.read()
        assert "window.location.origin" in content


# ---------------------------------------------------------------------------
# S5: CLI callback scheme check
# ---------------------------------------------------------------------------

class TestCliCallbackSchemeCheck:
    """Verify CLI callback validates redirect scheme."""

    def test_checks_protocol(self):
        path = os.path.join(
            os.path.dirname(os.path.dirname(__file__)),
            "web", "src", "app", "cli", "callback", "page.tsx",
        )
        with open(path) as f:
            content = f.read()
        assert 'protocol !== "http:"' in content or "protocol !== 'http:'" in content


# ---------------------------------------------------------------------------
# S6: Billing redirect HTTPS enforcement
# ---------------------------------------------------------------------------

class TestBillingRedirectHttps:
    """Verify billing redirect enforces HTTPS in production."""

    def test_production_https_check(self):
        from api import billing
        source = inspect.getsource(billing._validate_redirect_url)
        assert "production" in source.lower() or "ENVIRONMENT" in source

    def test_rejects_http_in_prod_mode(self):
        from api import billing
        source = inspect.getsource(billing._validate_redirect_url)
        assert '"https"' in source


# ---------------------------------------------------------------------------
# S7: Diff content verification
# ---------------------------------------------------------------------------

class TestDiffContentVerification:
    """Verify _apply_unified_diff checks content before deleting."""

    def test_verifies_content_before_pop(self):
        from api.ai import verified_fixer
        source = inspect.getsource(verified_fixer)
        # Should compare expected vs actual before removing
        assert "rstrip" in source or "expected" in source

    def test_aborts_on_mismatch(self):
        from api.ai import verified_fixer
        source = inspect.getsource(verified_fixer)
        assert "Diff mismatch" in source or "aborting" in source


# ---------------------------------------------------------------------------
# S8: GitHubConnect origin check
# ---------------------------------------------------------------------------

class TestGitHubConnectOrigin:
    """Verify GitHubConnect parses NEXT_PUBLIC_APP_URL as URL."""

    def test_uses_url_constructor(self):
        path = os.path.join(
            os.path.dirname(os.path.dirname(__file__)),
            "web", "src", "components", "GitHubConnect.tsx",
        )
        with open(path) as f:
            content = f.read()
        assert "new URL(" in content
        assert ".origin" in content


# ---------------------------------------------------------------------------
# B1: verified_fixer uses asyncio.to_thread for HTTP
# ---------------------------------------------------------------------------

class TestVerifiedFixerAsyncHttp:
    """Verify _find_existing_pr uses asyncio.to_thread."""

    def test_uses_to_thread(self):
        from api.ai.verified_fixer import PRCreator
        source = inspect.getsource(PRCreator._find_existing_pr)
        assert "asyncio.to_thread" in source or "to_thread" in source


# ---------------------------------------------------------------------------
# B2: dashboard uses asyncio.to_thread for Supabase
# ---------------------------------------------------------------------------

class TestDashboardAsyncHttp:
    """Verify list_regressions offloads _detect_regressions to thread."""

    def test_uses_to_thread(self):
        from api.routers import dashboard
        source = inspect.getsource(dashboard.list_regressions)
        assert "to_thread" in source


# ---------------------------------------------------------------------------
# B3: verified_fixer sandbox offloads file I/O
# ---------------------------------------------------------------------------

class TestSandboxAsyncFileIo:
    """Verify _verify_in_sandbox offloads file I/O."""

    def test_setup_sandbox_offloaded(self):
        from api.ai.verified_fixer import VerifiedFixer
        source = inspect.getsource(VerifiedFixer._verify_in_sandbox)
        assert "to_thread" in source


# ---------------------------------------------------------------------------
# A1-A2: Expanded SSRF filters
# ---------------------------------------------------------------------------

class TestExpandedSsrfFilters:
    """Verify SSRF filters cover more paths and patterns."""

    def test_ssrf_safe_paths_expanded(self):
        # SSRF safe paths now managed in scan-ignore.yaml and env-var pattern matching
        from autodebug.agent import tools
        source = inspect.getsource(tools)
        # The SSRF env-var filter checks for known safe URL patterns
        assert "_supabase_url" in source or "os.getenv" in source or "os.environ" in source

    def test_env_var_patterns_expanded(self):
        from autodebug.agent import tools
        source = inspect.getsource(tools)
        assert "_supabase_url" in source
        assert "api.github.com" in source


# ---------------------------------------------------------------------------
# A3-A7: scan-ignore.yaml expanded
# ---------------------------------------------------------------------------

class TestScanIgnoreExpanded:
    """Verify scan-ignore.yaml covers all known FP rules."""

    def test_has_insecure_hash(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)
        rules = [s.get("rule", "") for s in data["semgrep"]]
        assert any("insecure-hash" in r for r in rules)

    def test_has_avoid_pickle(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)
        rules = [s.get("rule", "") for s in data["semgrep"]]
        assert "avoid-pickle" in rules

    def test_has_insecure_file_permissions(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)
        rules = [s.get("rule", "") for s in data["semgrep"]]
        assert any("insecure-file-permissions" in r for r in rules)

    def test_has_blocking_call_suppression(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)
        rules = [s.get("rule", "") for s in data["semgrep"]]
        assert any("blocking" in r for r in rules)

    def test_review_ssrf_expanded(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
        # SSRF review suppression covers the db layer
        assert "api/db/" in ssrf_entry["paths"]


# ---------------------------------------------------------------------------
# R3: Rate limit retry jitter
# ---------------------------------------------------------------------------

class TestRateLimitJitter:
    """Verify rate limit retries use jitter."""

    def test_quiet_mode_has_jitter(self):
        from autodebug import cli_v2
        source = inspect.getsource(cli_v2._process_quiet)
        assert "uniform" in source or "random" in source

    def test_verbose_mode_has_jitter(self):
        from autodebug import cli_v2
        source = inspect.getsource(cli_v2._process_verbose)
        assert "uniform" in source or "random" in source


# ---------------------------------------------------------------------------
# R4: Event handler error filter broadened
# ---------------------------------------------------------------------------

class TestEventHandlerErrorFilter:
    """Verify event handler suppresses more cosmetic errors."""

    def test_filters_markup_errors(self):
        from autodebug import cli_v2
        source = inspect.getsource(cli_v2._process_verbose)
        assert "MarkupError" in source

    def test_filters_closed_file(self):
        from autodebug import cli_v2
        source = inspect.getsource(cli_v2._process_verbose)
        assert "closed file" in source


# ---------------------------------------------------------------------------
# P1: Redis client closed after health probe
# ---------------------------------------------------------------------------

class TestRedisClientClose:
    """Verify Redis health check closes connection."""

    def test_redis_client_closed(self):
        from api import main
        source = inspect.getsource(main._check_redis_health)
        assert "r.close()" in source

    def test_uses_finally_block(self):
        from api import main
        source = inspect.getsource(main._check_redis_health)
        assert "finally:" in source
