"""Tests for new security rules: SSRF-001, LOG-001, FILE-001, ERR-001, RES-001.

NOTE: This test file contains intentionally vulnerable code STRINGS
that are passed to the scanner for detection testing. These are not
executed — they are static analysis targets.
"""

from __future__ import annotations

import ast

from mcpaudit.models import Finding, Severity
from mcpaudit.rules.ssrf import SSRFRule
from mcpaudit.rules.sensitive_logging import SensitiveLoggingRule
from mcpaudit.rules.unsafe_file_write import UnsafeFileWriteRule
from mcpaudit.rules.error_handling import UnhandledToolErrorRule
from mcpaudit.rules.resource_exhaustion import ResourceExhaustionRule


def _check(rule, source: str) -> list[Finding]:
    tree = ast.parse(source)
    return rule.check(tree, source)


# ===========================================================================
# SSRF-001: Server-Side Request Forgery
# ===========================================================================

class TestSSRF:

    def test_requests_get_with_variable_url(self):
        source = 'import requests\ndef fetch(url):\n    return requests.get(url)\n'
        findings = _check(SSRFRule(), source)
        assert len(findings) == 1
        assert findings[0].rule_id == "SSRF-001"
        assert findings[0].severity == Severity.HIGH
        assert findings[0].cwe == "CWE-918"

    def test_requests_get_with_hardcoded_url_clean(self):
        source = 'import requests\nrequests.get("https://api.example.com/data")\n'
        findings = _check(SSRFRule(), source)
        assert len(findings) == 0

    def test_requests_post_with_fstring(self):
        source = 'import requests\ndef post(host):\n    requests.post(f"http://{host}/api")\n'
        findings = _check(SSRFRule(), source)
        assert len(findings) == 1

    def test_urllib_urlopen_with_variable(self):
        source = 'import urllib.request\ndef fetch(url):\n    urllib.request.urlopen(url)\n'
        findings = _check(SSRFRule(), source)
        assert len(findings) == 1

    def test_httpx_get_with_concat(self):
        source = 'import httpx\ndef fetch(base):\n    httpx.get("http://" + base)\n'
        findings = _check(SSRFRule(), source)
        assert len(findings) == 1

    def test_no_args_clean(self):
        source = 'import requests\nrequests.get()\n'
        findings = _check(SSRFRule(), source)
        assert len(findings) == 0


# ===========================================================================
# LOG-001: Sensitive Data in Logs
# ===========================================================================

class TestSensitiveLogging:

    def test_logging_password_variable(self):
        source = 'import logging\ndef login(password):\n    logging.info(password)\n'
        findings = _check(SensitiveLoggingRule(), source)
        assert len(findings) == 1
        assert findings[0].rule_id == "LOG-001"
        assert findings[0].severity == Severity.MEDIUM
        assert findings[0].cwe == "CWE-532"

    def test_logging_safe_variable_clean(self):
        source = 'import logging\ndef process(username):\n    logging.info(username)\n'
        findings = _check(SensitiveLoggingRule(), source)
        assert len(findings) == 0

    def test_print_api_key(self):
        source = 'def debug(api_key):\n    print(api_key)\n'
        findings = _check(SensitiveLoggingRule(), source)
        assert len(findings) == 1

    def test_logging_token_in_fstring(self):
        source = 'import logging\ndef show(token):\n    logging.debug(f"got {token}")\n'
        findings = _check(SensitiveLoggingRule(), source)
        assert len(findings) == 1

    def test_logging_secret_mixed_case(self):
        source = 'import logging\ndef show(user_Secret):\n    logging.error(user_Secret)\n'
        findings = _check(SensitiveLoggingRule(), source)
        assert len(findings) == 1

    def test_logging_constant_string_clean(self):
        source = 'import logging\nlogging.info("Application started")\n'
        findings = _check(SensitiveLoggingRule(), source)
        assert len(findings) == 0


# ===========================================================================
# FILE-001: Unsafe File Write
# ===========================================================================

class TestUnsafeFileWrite:

    def test_open_write_with_variable_path(self):
        source = 'def save(path):\n    f = open(path, "w")\n'
        findings = _check(UnsafeFileWriteRule(), source)
        assert len(findings) == 1
        assert findings[0].rule_id == "FILE-001"
        assert findings[0].severity == Severity.HIGH
        assert findings[0].cwe == "CWE-73"

    def test_open_write_hardcoded_path_clean(self):
        source = 'f = open("/var/log/app.log", "w")\n'
        findings = _check(UnsafeFileWriteRule(), source)
        assert len(findings) == 0

    def test_open_read_mode_clean(self):
        source = 'def load(path):\n    f = open(path, "r")\n'
        findings = _check(UnsafeFileWriteRule(), source)
        assert len(findings) == 0

    def test_shutil_copy_dynamic_dest(self):
        source = 'import shutil\ndef backup(src, dest):\n    shutil.copy(src, dest)\n'
        findings = _check(UnsafeFileWriteRule(), source)
        assert len(findings) == 1

    def test_shutil_move_dynamic_dest(self):
        source = 'import shutil\ndef mv(src, target):\n    shutil.move(src, target)\n'
        findings = _check(UnsafeFileWriteRule(), source)
        assert len(findings) == 1

    def test_open_with_mode_keyword(self):
        source = 'def save(path):\n    f = open(path, mode="w")\n'
        findings = _check(UnsafeFileWriteRule(), source)
        assert len(findings) == 1

    def test_path_open_write(self):
        source = 'from pathlib import Path\ndef save(p):\n    p.open("w")\n'
        findings = _check(UnsafeFileWriteRule(), source)
        assert len(findings) == 1


# ===========================================================================
# ERR-001: Unhandled Exceptions in Tool Functions
# ===========================================================================

class TestUnhandledToolError:

    def test_tool_function_without_try(self):
        source = 'async def handle_tool(args):\n    return process(args)\n'
        findings = _check(UnhandledToolErrorRule(), source)
        assert len(findings) == 1
        assert findings[0].rule_id == "ERR-001"
        assert findings[0].severity == Severity.MEDIUM
        assert findings[0].cwe == "CWE-755"

    def test_tool_function_with_try_clean(self):
        source = (
            'async def handle_tool(args):\n'
            '    try:\n'
            '        return process(args)\n'
            '    except Exception as e:\n'
            '        return {"error": str(e)}\n'
        )
        findings = _check(UnhandledToolErrorRule(), source)
        assert len(findings) == 0

    def test_regular_function_clean(self):
        source = 'def compute(x):\n    return x * 2\n'
        findings = _check(UnhandledToolErrorRule(), source)
        assert len(findings) == 0

    def test_decorated_tool_without_try(self):
        source = '@server.tool\nasync def read_file(path):\n    return open(path).read()\n'
        findings = _check(UnhandledToolErrorRule(), source)
        assert len(findings) == 1

    def test_decorated_tool_call_without_try(self):
        source = '@mcp.tool()\nasync def search(query):\n    return db.search(query)\n'
        findings = _check(UnhandledToolErrorRule(), source)
        assert len(findings) == 1

    def test_sync_tool_without_try(self):
        source = 'def my_tool_handler(args):\n    return run(args)\n'
        findings = _check(UnhandledToolErrorRule(), source)
        assert len(findings) == 1


# ===========================================================================
# RES-001: Unbounded Resource Consumption
# ===========================================================================

class TestResourceExhaustion:

    def test_file_read_no_size(self):
        source = 'def load(f):\n    data = f.read()\n'
        findings = _check(ResourceExhaustionRule(), source)
        assert len(findings) == 1
        assert findings[0].rule_id == "RES-001"
        assert findings[0].severity == Severity.MEDIUM
        assert findings[0].cwe == "CWE-400"

    def test_file_read_with_size_clean(self):
        source = 'def load(f):\n    data = f.read(4096)\n'
        findings = _check(ResourceExhaustionRule(), source)
        assert len(findings) == 0

    def test_requests_get_no_timeout(self):
        source = 'import requests\nrequests.get("https://example.com")\n'
        findings = _check(ResourceExhaustionRule(), source)
        assert len(findings) == 1

    def test_requests_get_with_timeout_clean(self):
        source = 'import requests\nrequests.get("https://example.com", timeout=30)\n'
        findings = _check(ResourceExhaustionRule(), source)
        assert len(findings) == 0

    def test_subprocess_no_timeout(self):
        source = 'import subprocess\nsubprocess.run(["ls"])\n'
        findings = _check(ResourceExhaustionRule(), source)
        assert len(findings) == 1

    def test_subprocess_with_timeout_clean(self):
        source = 'import subprocess\nsubprocess.run(["ls"], timeout=30)\n'
        findings = _check(ResourceExhaustionRule(), source)
        assert len(findings) == 0

    def test_multiple_violations(self):
        source = (
            'import requests, subprocess\n'
            'def bad():\n'
            '    data = f.read()\n'
            '    requests.post("http://x")\n'
            '    subprocess.call(["cmd"])\n'
        )
        findings = _check(ResourceExhaustionRule(), source)
        assert len(findings) == 3
