"""
Tests for Phase 30: Production Hardening.
"""

import time
import pytest


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    channels = tmp_path / "channels"
    sessions = tmp_path / "sessions"
    for d in (channels, sessions, tmp_path / "tasks", tmp_path / "profiles",
              tmp_path / "payments", tmp_path / "federation", tmp_path / "social",
              tmp_path / "api_keys"):
        d.mkdir(parents=True, exist_ok=True)

    import brynq.server as srv
    monkeypatch.setattr(srv, "BROKER_DIR", tmp_path)
    monkeypatch.setattr(srv, "CHANNELS_DIR", channels)
    monkeypatch.setattr(srv, "SESSIONS_DIR", sessions)

    import brynq.hardening as h
    monkeypatch.setattr(h, "BROKER_DIR", tmp_path)
    monkeypatch.setattr(h, "CHANNELS_DIR", channels)
    monkeypatch.setattr(h, "SESSIONS_DIR", sessions)


class TestSanitization:
    def test_clean_text(self):
        from brynq.hardening import sanitize_text
        text, warnings = sanitize_text("Hello world")
        assert text == "Hello world"
        assert warnings == []

    def test_script_blocked(self):
        from brynq.hardening import sanitize_text
        text, warnings = sanitize_text("Hello <script>alert('xss')</script>")
        assert "[BLOCKED]" in text
        assert any("Blocked" in w for w in warnings)

    def test_truncation(self):
        from brynq.hardening import sanitize_text
        text, warnings = sanitize_text("x" * 200, max_length=100)
        assert len(text) == 100
        assert any("Truncated" in w for w in warnings)

    def test_null_bytes(self):
        from brynq.hardening import sanitize_text
        text, _ = sanitize_text("hello\x00world")
        assert "\x00" not in text

    def test_html_escape(self):
        from brynq.hardening import sanitize_text
        text, _ = sanitize_text("<b>bold</b>")
        assert "<b>" not in text
        assert "&lt;b&gt;" in text

    def test_credential_warning(self):
        from brynq.hardening import sanitize_text
        _, warnings = sanitize_text("My key is sk-abc123")
        assert any("credential" in w.lower() for w in warnings)

    def test_non_string(self):
        from brynq.hardening import sanitize_text
        text, warnings = sanitize_text(12345)
        assert text == "12345"


class TestValidation:
    def test_valid_identifier(self):
        from brynq.hardening import validate_identifier
        ok, _ = validate_identifier("agent-123_abc")
        assert ok is True

    def test_empty_identifier(self):
        from brynq.hardening import validate_identifier
        ok, msg = validate_identifier("")
        assert ok is False

    def test_long_identifier(self):
        from brynq.hardening import validate_identifier
        ok, _ = validate_identifier("x" * 65)
        assert ok is False

    def test_invalid_chars(self):
        from brynq.hardening import validate_identifier
        ok, _ = validate_identifier("agent@evil.com")
        assert ok is False

    def test_valid_email(self):
        from brynq.hardening import validate_email
        ok, _ = validate_email("test@brynq.ai")
        assert ok is True

    def test_invalid_email(self):
        from brynq.hardening import validate_email
        ok, _ = validate_email("not-an-email")
        assert ok is False

    def test_valid_url(self):
        from brynq.hardening import validate_url
        ok, _ = validate_url("https://brynq.ai/api")
        assert ok is True

    def test_invalid_url(self):
        from brynq.hardening import validate_url
        ok, _ = validate_url("ftp://bad-protocol")
        assert ok is False


class TestRateLimiter:
    def test_allow(self):
        from brynq.hardening import RateLimiter
        rl = RateLimiter(max_tokens=5, refill_rate=1.0)
        ok, info = rl.allow("agent-1")
        assert ok is True
        assert info["remaining_tokens"] >= 0

    def test_exhaustion(self):
        from brynq.hardening import RateLimiter
        rl = RateLimiter(max_tokens=3, refill_rate=0.0)  # No refill
        rl.allow("a")
        rl.allow("a")
        rl.allow("a")
        ok, info = rl.allow("a")
        assert ok is False
        assert "retry_after_seconds" in info

    def test_reset(self):
        from brynq.hardening import RateLimiter
        rl = RateLimiter(max_tokens=2, refill_rate=0.0)
        rl.allow("a")
        rl.allow("a")
        rl.reset("a")
        ok, _ = rl.allow("a")
        assert ok is True

    def test_get_status(self):
        from brynq.hardening import RateLimiter
        rl = RateLimiter(max_tokens=10)
        status = rl.get_status("unknown")
        assert status["remaining_tokens"] == 10

    def test_global_limiter(self):
        from brynq.hardening import check_rate_limit
        ok, _ = check_rate_limit("test-agent")
        assert ok is True


class TestCircuitBreaker:
    def test_closed_allows(self):
        from brynq.hardening import CircuitBreaker
        cb = CircuitBreaker("test", failure_threshold=3)
        assert cb.allow_request() is True
        assert cb.state == "closed"

    def test_opens_after_failures(self):
        from brynq.hardening import CircuitBreaker
        cb = CircuitBreaker("test", failure_threshold=3)
        cb.record_failure()
        cb.record_failure()
        cb.record_failure()
        assert cb.state == "open"
        assert cb.allow_request() is False

    def test_half_open_after_timeout(self):
        from brynq.hardening import CircuitBreaker
        cb = CircuitBreaker("test", failure_threshold=2, recovery_timeout=0.01)
        cb.record_failure()
        cb.record_failure()
        assert cb.state == "open"
        time.sleep(0.02)
        assert cb.allow_request() is True
        assert cb.state == "half"

    def test_half_to_closed_on_success(self):
        from brynq.hardening import CircuitBreaker
        cb = CircuitBreaker("test", failure_threshold=1, recovery_timeout=0.01)
        cb.record_failure()
        time.sleep(0.02)
        cb.allow_request()  # moves to half
        cb.record_success()
        assert cb.state == "closed"

    def test_get_status(self):
        from brynq.hardening import CircuitBreaker
        cb = CircuitBreaker("svc")
        status = cb.get_status()
        assert status["name"] == "svc"
        assert status["state"] == "closed"


class TestHealthCheck:
    def test_deep_check(self):
        from brynq.hardening import deep_health_check
        result = deep_health_check()
        assert result["status"] in ("healthy", "degraded", "critical")
        assert "checks" in result
        assert result["subsystem_count"] > 0

    def test_all_healthy(self, tmp_path):
        from brynq.hardening import deep_health_check
        result = deep_health_check()
        assert result["healthy_count"] >= 5  # at least the dirs we created


class TestPayloadValidation:
    def test_valid_payload(self):
        from brynq.hardening import validate_payload
        schema = {
            "name": {"type": "str", "required": True, "max_length": 50},
            "age": {"type": "int", "required": False, "min_value": 0},
        }
        ok, errors = validate_payload({"name": "Test", "age": 5}, schema)
        assert ok is True
        assert errors == []

    def test_missing_required(self):
        from brynq.hardening import validate_payload
        schema = {"name": {"type": "str", "required": True}}
        ok, errors = validate_payload({}, schema)
        assert ok is False
        assert any("required" in e.lower() for e in errors)

    def test_wrong_type(self):
        from brynq.hardening import validate_payload
        schema = {"count": {"type": "int", "required": True}}
        ok, errors = validate_payload({"count": "five"}, schema)
        assert ok is False

    def test_too_long(self):
        from brynq.hardening import validate_payload
        schema = {"name": {"type": "str", "max_length": 5}}
        ok, errors = validate_payload({"name": "toolong"}, schema)
        assert ok is False

    def test_out_of_range(self):
        from brynq.hardening import validate_payload
        schema = {"score": {"type": "float", "min_value": 0, "max_value": 100}}
        ok, errors = validate_payload({"score": 150}, schema)
        assert ok is False

    def test_invalid_choice(self):
        from brynq.hardening import validate_payload
        schema = {"status": {"type": "str", "choices": ["active", "inactive"]}}
        ok, errors = validate_payload({"status": "deleted"}, schema)
        assert ok is False

    def test_optional_missing(self):
        from brynq.hardening import validate_payload
        schema = {"opt": {"type": "str", "required": False}}
        ok, errors = validate_payload({}, schema)
        assert ok is True
