import json
from decimal import Decimal

from pydantic import SecretStr

from binance_quant.audit_log import AuditLogger, audit_event


def test_audit_logger_default_size_limit_is_20m(tmp_path):
    logger = AuditLogger(log_dir=tmp_path / "logs")

    assert logger.max_bytes == 20 * 1024 * 1024


def read_events(path):
    events = []
    for block in path.read_text(encoding="utf-8").split("=" * 80):
        if "\nJSON:\n" not in block:
            continue
        raw_json = block.split("\nJSON:\n", 1)[1].splitlines()[0]
        events.append(json.loads(raw_json))
    return events


def test_audit_logger_writes_jsonl_event_with_serialized_payload(tmp_path):
    logger = AuditLogger(log_dir=tmp_path / "logs")

    logger.log(
        "strategy.score",
        params={"symbol": "BTCUSDT", "threshold": Decimal("4.5")},
        formula={"spread_bps": "(ask - bid) / midpoint * 10000"},
        result={"score": Decimal("5.25"), "approved": True},
    )

    log_text = (tmp_path / "logs" / "audit.log").read_text(encoding="utf-8")
    assert "Event: strategy.score" in log_text
    assert "Params:" in log_text
    assert '"threshold": "4.5"' in log_text
    assert "Formula:" in log_text
    assert "Result:" in log_text
    assert "JSON:" in log_text

    [event] = read_events(tmp_path / "logs" / "audit.log")
    assert event["event"] == "strategy.score"
    assert event["params"] == {"symbol": "BTCUSDT", "threshold": "4.5"}
    assert event["formula"] == {"spread_bps": "(ask - bid) / midpoint * 10000"}
    assert event["result"] == {"score": "5.25", "approved": True}
    assert event["level"] == "INFO"
    assert event["timestamp"].endswith("+00:00")


def test_audit_logger_redacts_sensitive_keys_and_secret_values(tmp_path):
    logger = AuditLogger(log_dir=tmp_path / "logs")

    logger.log(
        "api.request",
        params={
            "api_key": "visible-key",
            "apiSecret": "hidden-secret",
            "signature": "abc123",
            "nested": {"password": SecretStr("pw"), "recvWindow": "5000"},
        },
        result={"headers": {"X-MBX-APIKEY": "visible-key"}, "ok": True},
    )

    [event] = read_events(tmp_path / "logs" / "audit.log")
    assert event["params"]["api_key"] == "***REDACTED***"
    assert event["params"]["apiSecret"] == "***REDACTED***"
    assert event["params"]["signature"] == "***REDACTED***"
    assert event["params"]["nested"]["password"] == "***REDACTED***"
    assert event["params"]["nested"]["recvWindow"] == "5000"
    assert event["result"]["headers"]["X-MBX-APIKEY"] == "***REDACTED***"


def test_audit_logger_rotates_when_size_limit_is_exceeded(tmp_path):
    logger = AuditLogger(log_dir=tmp_path / "logs", max_bytes=1, backup_count=1)

    logger.log("first", result={"message": "first"})
    logger.log("second", result={"message": "second"})

    active = (tmp_path / "logs" / "audit.log").read_text(encoding="utf-8")
    backup = (tmp_path / "logs" / "audit.log.1").read_text(encoding="utf-8")
    assert "Event: second" in active
    assert "Event: first" in backup
    assert not (tmp_path / "logs" / "audit.log.2").exists()


def test_audit_logger_limits_single_entry_size(tmp_path):
    logger = AuditLogger(log_dir=tmp_path / "logs", max_entry_bytes=700)

    logger.log("huge.context", result={"payload": "x" * 10_000})

    log_text = (tmp_path / "logs" / "audit.log").read_text(encoding="utf-8")
    assert len(log_text.encode("utf-8")) <= 700
    assert "Event: huge.context" in log_text
    assert "truncated" in log_text
    assert "x" * 1000 not in log_text


def test_audit_event_uses_current_working_directory_logs(tmp_path, monkeypatch):
    monkeypatch.chdir(tmp_path)

    audit_event("logic.point", result={"value": Decimal("1.23")})

    [event] = read_events(tmp_path / "logs" / "audit.log")
    assert event["event"] == "logic.point"
    assert event["result"] == {"value": "1.23"}
