"""Tests for agentpaas core components."""
import pytest
from agentpaas.config import AgentPaaSConfig
from agentpaas.engine.cache import CompileCache
from agentpaas.engine.retry import RetryPolicy, CircuitBreaker
from agentpaas.billing.pricing import calculate_cost, PRICING
from agentpaas.observability.metrics import Metrics
from agentpaas.tenant.rbac import check_permission
from agentpaas.api.middleware.auth import TenantContext, hash_key, verify_key_hash


# Config

def test_config_defaults():
    cfg = AgentPaaSConfig()
    assert cfg.port == 8000
    assert "sqlite" in cfg.database_url


# CompileCache

def test_cache_hit_miss():
    cache = CompileCache(maxsize=3)
    cache.put("h1", "term1")
    assert cache.get("h1") == "term1"
    assert cache.get("h2") is None
    assert cache.hits == 1
    assert cache.misses == 1


def test_cache_lru():
    cache = CompileCache(maxsize=2)
    cache.put("a", 1)
    cache.put("b", 2)
    cache.put("c", 3)  # evicts "a"
    assert cache.get("a") is None
    assert cache.get("c") == 3


# RetryPolicy

def test_retry_should_retry():
    rp = RetryPolicy(max_attempts=3)
    assert rp.should_retry(TimeoutError("timeout"), 0) == True
    assert rp.should_retry(TimeoutError("timeout"), 3) == False
    assert rp.should_retry(ValueError("bad"), 0) == False


def test_retry_delay():
    rp = RetryPolicy(base_delay=1.0, exponential_base=2.0, max_delay=10.0)
    assert rp.get_delay(0) == 1.0
    assert rp.get_delay(1) == 2.0
    assert rp.get_delay(2) == 4.0
    assert rp.get_delay(10) == 10.0  # capped


# CircuitBreaker

def test_circuit_breaker_flow():
    cb = CircuitBreaker(failure_threshold=3, recovery_timeout=0.1)
    assert cb.allow_request() == True
    cb.record_failure()
    cb.record_failure()
    assert cb.allow_request() == True  # not yet at threshold
    cb.record_failure()
    assert cb.state == CircuitBreaker.OPEN
    assert cb.allow_request() == False


def test_circuit_breaker_recovery():
    import time
    cb = CircuitBreaker(failure_threshold=2, recovery_timeout=0.1)
    cb.record_failure()
    cb.record_failure()
    assert cb.state == CircuitBreaker.OPEN
    time.sleep(0.15)
    assert cb.allow_request() == True  # half-open
    cb.record_success()
    assert cb.state == CircuitBreaker.CLOSED


# Billing

def test_pricing():
    cost = calculate_cost("gpt-4o", 1000, 500)
    assert cost > 0
    assert cost == (1000 / 1_000_000 * 2.5 + 500 / 1_000_000 * 10.0)


def test_pricing_unknown_model():
    cost = calculate_cost("unknown-model", 1000, 1000)
    assert cost > 0  # uses default pricing


# Metrics

def test_metrics_counter():
    m = Metrics()
    m.inc("requests", {"agent": "a1"})
    m.inc("requests", {"agent": "a1"})
    snap = m.snapshot()
    assert snap["counters"]["requests{agent=a1}"] == 2


def test_metrics_observe():
    m = Metrics()
    m.observe("latency", 100.0)
    m.observe("latency", 200.0)
    snap = m.snapshot()
    assert snap["histograms"]["latency"]["mean"] == 150.0


# RBAC

def test_rbac_admin():
    ctx = TenantContext("t1", "org", "pro", role="admin")
    assert check_permission(ctx, "tenants:read") == True
    assert check_permission(ctx, "agents:execute") == True


def test_rbac_viewer():
    ctx = TenantContext("t1", "org", "pro", role="viewer")
    assert check_permission(ctx, "agents:read") == True
    assert check_permission(ctx, "agents:write") == False


def test_rbac_scopes():
    ctx = TenantContext("t1", "org", "pro", role="viewer", scopes=["agents:execute"])
    assert check_permission(ctx, "agents:execute") == True


# Auth

def test_hash_key():
    # FIX-08: hash_key now uses PBKDF2 with a random salt per call.
    # Format: "pbkdf2:<32-hex salt>:<64-hex hash>" — 7 + 32 + 1 + 64 = 104 chars.
    h1 = hash_key("ap_test123")
    h2 = hash_key("ap_test123")
    assert h1.startswith("pbkdf2:")
    assert len(h1) == 104
    # Random salt → two calls on the same input must NOT collide.
    assert h1 != h2, "random-salt PBKDF2 should produce different hashes per call"
    # But both must verify back to the original key.
    assert verify_key_hash("ap_test123", h1)
    assert verify_key_hash("ap_test123", h2)
    assert not verify_key_hash("wrong_key", h1)
