from datetime import datetime, timedelta

from app import rules
from app.redact import redact


def test_sqli_detected_and_benign_ignored():
    assert rules.detect_sqli("id=1' OR '1'='1")
    assert rules.detect_sqli("q=1%20UNION%20SELECT%20username,password%20FROM%20users")
    assert rules.detect_sqli("x=1; DROP TABLE users")
    assert rules.detect_sqli("id=5&sort=name") == []
    assert rules.detect_sqli("search=the quick brown fox") == []


def test_encoded_payload_is_decoded():
    assert rules.detect_xss("q=%3Cscript%3Ealert(1)%3C%2Fscript%3E")
    assert rules.detect_xss("q=%253Cscript%253E")  # double-encoded


def test_xss_and_traversal():
    assert rules.detect_xss('<img src=x onerror=alert(1)>')
    assert rules.detect_xss("hello world") == []
    assert rules.detect_path_traversal("file=../../etc/passwd")
    assert rules.detect_path_traversal("file=..%2f..%2fwindows%2fwin.ini")
    assert rules.detect_path_traversal("file=report.pdf") == []


def test_analyze_http_request_marks_suspected_not_confirmed():
    f = rules.analyze_http_request("/products.php", "id=1' OR '1'='1", ip="203.0.113.9")
    assert f and f[0].category == "sql_injection"
    assert all(x.verification == "suspected" for x in f)
    assert rules.analyze_http_request("/index.php", "page=2") == []


def test_brute_force_thresholds():
    assert rules.check_brute_force(4, 5, "1.2.3.4", 10) is None
    assert rules.check_brute_force(5, 5, "1.2.3.4", 10).risk == "high"
    assert rules.check_brute_force(15, 5, "1.2.3.4", 10).risk == "critical"


def test_port_scan():
    now = datetime(2026, 10, 24, 12, 0, 0)
    recs = [(p, now - timedelta(seconds=1)) for p in range(1, 21)]
    assert rules.check_port_scan(recs, "9.9.9.9", now, 15, 60).category == "port_scan"
    assert rules.check_port_scan(recs[:5], "9.9.9.9", now, 15, 60) is None
    old = [(p, now - timedelta(seconds=300)) for p in range(1, 21)]
    assert rules.check_port_scan(old, "9.9.9.9", now, 15, 60) is None


def test_traffic_spike():
    assert rules.check_traffic_spike(50, [10, 12], 3.0, 100) is None        # di bawah minimum
    assert rules.check_traffic_spike(150, [100, 110, 90], 3.0, 100) is None  # normal
    assert rules.check_traffic_spike(500, [100, 110, 90], 3.0, 100) is not None


def test_file_integrity():
    assert rules.check_file_integrity("/etc/a", "aaa", "aaa") == ("ok", None)
    assert rules.check_file_integrity("/etc/a", None, "aaa") == ("new", None)
    st, f = rules.check_file_integrity("/etc/a", "a" * 64, "b" * 64)
    assert st == "modified" and f.risk == "critical"
    st, f = rules.check_file_integrity("/etc/a", "a" * 64, None)
    assert st == "missing" and f.category == "file_tampering"


def test_admin_activity_and_sensor_silence():
    night = datetime(2026, 10, 24, 3, 0)
    day = datetime(2026, 10, 24, 10, 0)
    assert rules.check_admin_activity("delete_user", night, 6, 22, "1.1.1.1") is not None
    assert rules.check_admin_activity("delete_user", day, 6, 22, "1.1.1.1", {"1.1.1.1"}) is None
    now = datetime(2026, 10, 24, 12, 0)
    assert rules.check_sensor_silent(now - timedelta(seconds=400), now, 180)
    assert not rules.check_sensor_silent(now - timedelta(seconds=30), now, 180)


def test_redaction():
    out = redact("GET /login?user=a&password=Secret123&token=abc.def Cookie: sessionid=xyz Authorization: Bearer abcdefghijkl")
    assert "Secret123" not in out and "xyz" not in out and "abcdefghijkl" not in out
    assert "[REDACTED]" in out
    assert len(redact("a" * 2000)) == 500
