"""
Tests for GateWay Uber-style queue system.

Two-mode pricing:
- Performance (fast=True):  +50% margin, immediate execution
- Standard (fast=False):    +30% margin, can wait up to 10 min
"""

import time

import pytest

from verlex_server.gateway.core.queue import (
    PricingTier,
    PriorityQueue,
    QueuedRequest,
    QueueStats,
    calculate_price,
    estimate_wait_time,
)


class TestPricingTiers:
    """Tests for pricing tier multipliers."""

    def test_performance_premium(self):
        """Performance users pay 50% premium."""
        base_price = 10.0
        final, breakdown = calculate_price(base_price, PricingTier.PERFORMANCE)

        assert breakdown["tier_multiplier"] == 1.50
        assert final == 15.0  # 10 * 1.50

    def test_standard_premium(self):
        """Standard users pay 30% premium."""
        base_price = 10.0
        final, breakdown = calculate_price(base_price, PricingTier.STANDARD)

        assert breakdown["tier_multiplier"] == pytest.approx(1.30)
        assert final == pytest.approx(13.0)  # 10 * 1.30

    def test_savings_calculation(self):
        """Verify savings vs performance is calculated correctly."""
        base_price = 10.0
        _, breakdown = calculate_price(base_price, PricingTier.STANDARD)

        # Standard pays $13.00, Performance would pay $15.00
        # Savings = $15.00 - $13.00 = $2.00
        expected_savings = (1.50 * 10) - 13.0
        assert breakdown["savings_vs_performance"] == pytest.approx(expected_savings, abs=0.01)


class TestQueuedRequest:
    """Tests for QueuedRequest priority scoring."""

    def test_performance_user_constant_score(self):
        """Performance users have constant high priority (score = 5)."""
        request = QueuedRequest(
            request_id="req_1",
            job_id="job_1",
            user_id="user_1",
            tier=PricingTier.PERFORMANCE,
        )

        time.sleep(0.1)

        assert request.get_priority_score() == 5.0

    def test_standard_user_initial_low_priority(self):
        """Standard users start with low priority (score = 100)."""
        request = QueuedRequest(
            request_id="req_1",
            job_id="job_1",
            user_id="user_1",
            tier=PricingTier.STANDARD,
        )

        score = request.get_priority_score()
        assert score >= 95

    def test_standard_priority_ages_with_time(self):
        """Standard priority increases (score decreases) over time."""
        request = QueuedRequest(
            request_id="req_1",
            job_id="job_1",
            user_id="user_1",
            tier=PricingTier.STANDARD,
            submitted_at=time.time() - 300,  # 5 minutes ago
        )

        score = request.get_priority_score()
        assert 40 <= score <= 60

    def test_standard_reaches_performance_level_at_10_mins(self):
        """Standard priority equals Performance user at 10 minutes."""
        request = QueuedRequest(
            request_id="req_1",
            job_id="job_1",
            user_id="user_1",
            tier=PricingTier.STANDARD,
            submitted_at=time.time() - 600,  # 10 minutes ago
        )

        score = request.get_priority_score()
        assert score <= 10  # Close to Performance's 5

    def test_standard_vs_performance_ordering(self):
        """New standard user should be behind performance user."""
        performance_req = QueuedRequest(
            request_id="performance",
            job_id="job_p",
            user_id="user_p",
            tier=PricingTier.PERFORMANCE,
        )

        standard_req = QueuedRequest(
            request_id="standard",
            job_id="job_s",
            user_id="user_s",
            tier=PricingTier.STANDARD,
        )

        assert performance_req.get_priority_score() < standard_req.get_priority_score()

        sorted_reqs = sorted([standard_req, performance_req], key=lambda r: r.get_priority_score())
        assert sorted_reqs[0].request_id == "performance"

    def test_old_standard_matches_new_performance(self):
        """10-min old standard should match performance (equal priority)."""
        old_standard = QueuedRequest(
            request_id="old_standard",
            job_id="job_old",
            user_id="user_old",
            tier=PricingTier.STANDARD,
            submitted_at=time.time() - 600,
        )

        new_performance = QueuedRequest(
            request_id="new_performance",
            job_id="job_new",
            user_id="user_new",
            tier=PricingTier.PERFORMANCE,
        )

        old_score = old_standard.get_priority_score()
        new_score = new_performance.get_priority_score()

        assert abs(old_score - new_score) < 5


class TestPriorityQueue:
    """Tests for the priority queue."""

    def test_submit_and_pop(self):
        """Test basic submit and pop operations."""
        queue = PriorityQueue()

        request = queue.submit(
            request_id="req_1",
            job_id="job_1",
            user_id="user_1",
            tier=PricingTier.PERFORMANCE,
            base_price_per_hour=10.0,
        )

        assert len(queue) == 1
        # Empty queue returns MIN_MULTIPLIER (0.90): 10.0 * 1.50 * 0.90 = 13.50
        assert request.final_price_per_hour == pytest.approx(13.50, abs=0.01)

        popped = queue.pop()
        assert popped.request_id == "req_1"
        assert len(queue) == 0

    def test_performance_user_first(self):
        """Performance users should be served before standard users."""
        queue = PriorityQueue()

        queue.submit("standard", "job_s", "user_s", PricingTier.STANDARD)
        queue.submit("performance", "job_p", "user_p", PricingTier.PERFORMANCE)

        first = queue.pop()
        assert first.request_id == "performance"

    def test_standard_position_improves_with_time(self):
        """Standard user's position should improve as they wait."""
        queue = PriorityQueue()

        queue.submit("performance", "job_p", "user_p", PricingTier.PERFORMANCE)

        queue.submit(
            "old_standard", "job_old", "user_old",
            PricingTier.STANDARD,
        )
        queue._requests["old_standard"].submitted_at = time.time() - 540

        queue.submit("new_standard", "job_new", "user_new", PricingTier.STANDARD)

        first = queue.pop()
        assert first.request_id == "performance"

        second = queue.pop()
        assert second.request_id == "old_standard"

        third = queue.pop()
        assert third.request_id == "new_standard"

    def test_max_wait_guarantee(self):
        """Standard users should have position estimate honoring 10-min max."""
        queue = PriorityQueue()

        queue.submit("standard", "job_s", "user_s", PricingTier.STANDARD)

        estimated = queue.get_estimated_wait("standard")
        assert estimated <= 600

    def test_urgent_requests(self):
        """Identify requests approaching max wait time."""
        queue = PriorityQueue()

        queue.submit("urgent", "job_u", "user_u", PricingTier.STANDARD)
        queue._requests["urgent"].submitted_at = time.time() - 570

        queue.submit("new", "job_n", "user_n", PricingTier.STANDARD)

        urgent = queue.get_urgent_requests()

        assert len(urgent) == 1
        assert urgent[0].request_id == "urgent"

    def test_queue_stats(self):
        """Test queue statistics."""
        queue = PriorityQueue()

        queue.submit("p1", "job_1", "user_1", PricingTier.PERFORMANCE)
        queue.submit("s1", "job_2", "user_2", PricingTier.STANDARD)
        queue.submit("s2", "job_3", "user_3", PricingTier.STANDARD)

        stats = queue.get_stats()

        assert stats.total_requests == 3
        assert stats.performance_requests == 1
        assert stats.standard_requests == 2


class TestSurgeMultiplier:
    """Tests for surge pricing based on demand."""

    def test_low_demand_discount(self):
        """Low demand should result in discount."""
        base_price = 10.0
        final, breakdown = calculate_price(base_price, PricingTier.PERFORMANCE, demand_level=0.1)

        assert breakdown["surge_multiplier"] < 1.0
        assert "Low Demand" in breakdown["surge_description"]

    def test_high_demand_surge(self):
        """High demand should result in surge pricing."""
        base_price = 10.0
        final, breakdown = calculate_price(base_price, PricingTier.PERFORMANCE, demand_level=0.9)

        assert breakdown["surge_multiplier"] > 1.0
        assert "High Demand" in breakdown["surge_description"]

    def test_normal_demand_no_surge(self):
        """Normal demand should have no surge."""
        base_price = 10.0
        final, breakdown = calculate_price(base_price, PricingTier.PERFORMANCE, demand_level=0.5)

        assert breakdown["surge_multiplier"] == 1.0
        assert "Normal Demand" in breakdown["surge_description"]


class TestWaitTimeEstimation:
    """Tests for wait time estimation."""

    def test_performance_minimal_wait(self):
        """Performance users should have minimal wait."""
        stats = QueueStats(performance_requests=5)

        min_wait, max_wait = estimate_wait_time(PricingTier.PERFORMANCE, stats)

        assert min_wait == 0
        assert max_wait <= 60

    def test_standard_max_10_mins(self):
        """Standard users should have max 10 minute wait."""
        stats = QueueStats(performance_requests=10, standard_requests=20)

        min_wait, max_wait = estimate_wait_time(PricingTier.STANDARD, stats)

        assert max_wait == 600


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
