"""
Tests for the GateWay Billing System

Tests cover:
- Pricing engine with two-mode margins (Performance 50%, Standard 30%)
- Per-second metering
- Credit management
- Invoice generation
- Cost estimation
"""

import time
from decimal import Decimal

import pytest

import verlex_server.gateway.backend.billing as billing_module
import verlex_server.gateway.backend.database as database_module
from verlex_server.gateway.backend.billing import (
    GATEWAY_MARGINS,
    PRICING_TIERS,
    BillingService,
    CreditManager,
    ExecutionMode,
    InvoiceGenerator,
    PricingEngine,
    UsageMeter,
    UsageRecord,
    configure_billing,
    get_billing_service,
)


@pytest.fixture(autouse=True)
def reset_billing_state():
    """Reset global billing state before each test to ensure test isolation."""
    billing_module._supabase_client = None
    database_module._supabase_client = None
    billing_module._supabase_client = False
    yield
    billing_module._supabase_client = None
    database_module._supabase_client = None


class TestPricingEngine:
    """Tests for the PricingEngine class."""

    def setup_method(self):
        """Set up test fixtures."""
        self.engine = PricingEngine()

    def test_gateway_margins_defined(self):
        """Test that all mode margins are defined correctly (as percentages)."""
        assert GATEWAY_MARGINS[ExecutionMode.PERFORMANCE] == Decimal("0.50")  # 50%
        assert GATEWAY_MARGINS[ExecutionMode.STANDARD] == Decimal("0.30")     # 30%

    def test_get_margin_for_mode(self):
        """Test getting margin percentage for each mode."""
        assert self.engine.get_margin_for_mode("P") == Decimal("0.50")   # 50% Performance
        assert self.engine.get_margin_for_mode("S") == Decimal("0.30")   # 30% Standard

    def test_get_margin_for_legacy_modes(self):
        """Test legacy modes all map to Standard (30%)."""
        assert self.engine.get_margin_for_mode("Pa") == Decimal("0.30")  # Patient -> Standard
        assert self.engine.get_margin_for_mode("N") == Decimal("0.30")   # Normal -> Standard
        assert self.engine.get_margin_for_mode("F") == Decimal("0.30")   # Flexible -> Standard

    def test_get_margin_for_full_word_modes(self):
        """Test full word mode values from PricingTier.value (scheduler path)."""
        assert self.engine.get_margin_for_mode("performance") == Decimal("0.50")  # 50%
        assert self.engine.get_margin_for_mode("standard") == Decimal("0.30")     # 30%

    def test_calculate_price_standard_mode(self):
        """Test basic price calculation with Standard mode (30% margin)."""
        result = self.engine.calculate_price(
            provider_hourly_rate=1.00,
            duration_seconds=3600,
            mode="S",
        )

        assert result["provider_cost"] == 1.00
        assert result["gateway_margin_total"] == 0.30  # 30% of $1.00
        assert result["gross_total"] == 1.30
        assert result["net_total"] == 1.30

    def test_calculate_price_performance_mode(self):
        """Test Performance mode pricing (+50%)."""
        result = self.engine.calculate_price(
            provider_hourly_rate=1.00,
            duration_seconds=3600,
            mode="P",
        )

        assert result["gateway_margin_total"] == 0.50  # 50% of $1.00
        assert result["net_total"] == 1.50

    def test_calculate_price_per_second(self):
        """Test per-second billing accuracy."""
        # 30 minutes at $1/hr with 30% margin (Standard)
        result = self.engine.calculate_price(
            provider_hourly_rate=1.00,
            duration_seconds=1800,
            mode="S",
        )

        assert result["duration_hours"] == 0.5
        assert result["provider_cost"] == 0.50
        assert result["gateway_margin_total"] == 0.15  # 30% of $0.50
        assert result["net_total"] == 0.65  # $0.50 + $0.15

    def test_calculate_price_short_duration(self):
        """Test billing for short durations (seconds)."""
        result = self.engine.calculate_price(
            provider_hourly_rate=1.00,
            duration_seconds=60,
            mode="S",
        )

        assert abs(result["duration_hours"] - 0.0166667) < 0.001
        assert abs(result["provider_cost"] - 0.0166667) < 0.001

    def test_calculate_price_with_tier_discount(self):
        """Test price calculation with Pro tier discount (10% off gross)."""
        result = self.engine.calculate_price(
            provider_hourly_rate=1.00,
            duration_seconds=3600,
            mode="S",
            user_tier="pro",
        )

        assert result["tier_discount_percent"] == 10.0
        assert result["gross_total"] == 1.30  # $1.00 + 30% margin (Standard)
        assert result["discount_amount"] == 0.13  # 10% of $1.30
        assert abs(result["net_total"] - 1.17) < 0.001

    def test_calculate_price_with_enterprise_discount(self):
        """Test price calculation with Enterprise tier discount (20% off gross)."""
        result = self.engine.calculate_price(
            provider_hourly_rate=1.00,
            duration_seconds=3600,
            mode="S",
            user_tier="enterprise",
        )

        assert result["tier_discount_percent"] == 20.0
        assert result["gross_total"] == 1.30  # $1.00 + 30% margin (Standard)
        assert result["discount_amount"] == 0.26  # 20% of $1.30
        assert abs(result["net_total"] - 1.04) < 0.001

    def test_compare_mode_costs(self):
        """Test comparing costs across both modes."""
        results = self.engine.compare_mode_costs(
            provider_hourly_rate=1.00,
            duration_hours=1.0,
        )

        assert "P" in results
        assert "S" in results

        # Performance (50%) should be more expensive than Standard (30%)
        assert results["P"]["total_cost"] > results["S"]["total_cost"]

    def test_estimate_cost(self):
        """Test pre-execution cost estimation."""
        estimate = self.engine.estimate_cost(
            provider_hourly_rate=0.50,
            estimated_duration_hours=2.0,
            mode="S",
        )

        assert "estimated_cost" in estimate
        assert "hourly_rate" in estimate
        assert estimate["mode"] == "S"


class TestUsageMeter:
    """Tests for the UsageMeter class."""

    def setup_method(self):
        """Set up test fixtures."""
        self.meter = UsageMeter()

    def test_start_metering(self):
        """Test starting a metering session."""
        session_id = self.meter.start_metering(
            job_id="job_123",
            user_id="user_456",
            provider="gcp",
            instance_type="n1-standard-4",
            region="us-central1",
            provider_hourly_rate=0.50,
            mode="S",
        )

        assert session_id is not None
        assert len(self.meter._active_sessions) == 1

    def test_checkpoint(self):
        """Test checkpointing usage."""
        session_id = self.meter.start_metering(
            job_id="job_123",
            user_id="user_456",
            provider="gcp",
            instance_type="n1-standard-4",
            region="us-central1",
            provider_hourly_rate=0.50,
            mode="S",
        )

        time.sleep(0.1)

        checkpoint = self.meter.checkpoint(session_id)

        assert checkpoint is not None
        assert checkpoint["job_id"] == "job_123"
        assert checkpoint["compute_seconds"] > 0

    def test_stop_metering(self):
        """Test stopping metering and getting usage record."""
        session_id = self.meter.start_metering(
            job_id="job_123",
            user_id="user_456",
            provider="gcp",
            instance_type="n1-standard-4",
            region="us-central1",
            provider_hourly_rate=0.50,
            mode="S",
            gpu_type="T4",
        )

        time.sleep(0.1)

        record = self.meter.stop_metering(session_id)

        assert record is not None
        assert record.job_id == "job_123"
        assert record.user_id == "user_456"
        assert record.provider == "gcp"
        assert record.gpu_type == "T4"
        assert record.compute_seconds > 0
        assert record.total_cost > 0

        assert len(self.meter._active_sessions) == 0

    def test_stop_nonexistent_session(self):
        """Test stopping a non-existent session returns None."""
        result = self.meter.stop_metering("nonexistent_session")
        assert result is None

    def test_get_active_sessions(self):
        """Test listing active sessions."""
        self.meter.start_metering(
            job_id="job_1",
            user_id="user_1",
            provider="gcp",
            instance_type="n1-standard-4",
            region="us-central1",
            provider_hourly_rate=0.50,
            mode="S",
        )
        self.meter.start_metering(
            job_id="job_2",
            user_id="user_2",
            provider="aws",
            instance_type="g4dn.xlarge",
            region="us-east-1",
            provider_hourly_rate=0.52,
            mode="P",
        )

        sessions = self.meter.get_active_sessions()

        assert len(sessions) == 2
        job_ids = {s["job_id"] for s in sessions}
        assert "job_1" in job_ids
        assert "job_2" in job_ids


class TestCreditManager:
    """Tests for the CreditManager class."""

    def setup_method(self):
        """Set up test fixtures."""
        self.manager = CreditManager()

    def test_get_balance_new_user(self):
        """Test getting balance for new user creates zero balance."""
        balance = self.manager.get_balance("new_user")

        assert balance.user_id == "new_user"
        assert balance.balance == Decimal("0.00")

    def test_add_credits(self):
        """Test adding credits to balance."""
        balance = self.manager.add_credits(
            user_id="user_1",
            amount=50.00,
            reason="purchase",
        )

        assert balance.balance == Decimal("50.00")
        assert balance.lifetime_added == Decimal("50.00")

    def test_deduct_credits_success(self):
        """Test successfully deducting credits."""
        self.manager.add_credits("user_1", 100.00)

        success, remaining = self.manager.deduct_credits(
            user_id="user_1",
            amount=30.00,
            job_id="job_123",
        )

        assert success is True
        assert remaining == Decimal("70.00")

    def test_deduct_credits_insufficient(self):
        """Test deducting more than balance fails."""
        self.manager.add_credits("user_1", 10.00)

        success, remaining = self.manager.deduct_credits(
            user_id="user_1",
            amount=50.00,
        )

        assert success is False
        assert remaining == Decimal("10.00")

    def test_check_can_afford(self):
        """Test checking if user can afford a job."""
        self.manager.add_credits("user_1", 25.00)

        result = self.manager.check_can_afford("user_1", 20.00)
        assert result["can_afford"] is True
        assert result["shortfall"] == 0

        result = self.manager.check_can_afford("user_1", 50.00)
        assert result["can_afford"] is False
        assert result["shortfall"] == 25.00

    def test_lifetime_tracking(self):
        """Test lifetime credit tracking."""
        self.manager.add_credits("user_1", 100.00)
        self.manager.deduct_credits("user_1", 30.00)
        self.manager.add_credits("user_1", 50.00)
        self.manager.deduct_credits("user_1", 20.00)

        balance = self.manager.get_balance("user_1")

        assert balance.balance == Decimal("100.00")  # 100 - 30 + 50 - 20
        assert balance.lifetime_added == Decimal("150.00")
        assert balance.lifetime_used == Decimal("50.00")


class TestUsageRecord:
    """Tests for UsageRecord dataclass."""

    def test_usage_record_creation(self):
        """Test creating a usage record."""
        record = UsageRecord(
            user_id="user_1",
            job_id="job_1",
            compute_seconds=1800,
            provider_cost=Decimal("0.50"),
            gateway_margin=Decimal("0.15"),
            total_cost=Decimal("0.65"),
        )

        assert record.user_id == "user_1"
        assert record.compute_seconds == 1800
        assert record.total_cost == Decimal("0.65")

    def test_usage_record_to_dict(self):
        """Test converting usage record to dictionary."""
        record = UsageRecord(
            user_id="user_1",
            job_id="job_1",
            provider="gcp",
            compute_seconds=3600,
            total_cost=Decimal("1.30"),
        )

        d = record.to_dict()

        assert d["user_id"] == "user_1"
        assert d["provider"] == "gcp"
        assert d["compute_seconds"] == 3600
        assert d["total_cost"] == 1.30


class TestBillingService:
    """Tests for the main BillingService class."""

    def setup_method(self):
        """Set up test fixtures."""
        self.billing = BillingService()

    def test_estimate_job_cost(self):
        """Test job cost estimation."""
        estimate = self.billing.estimate_job_cost(
            provider_hourly_rate=1.00,
            estimated_duration_hours=2.0,
            mode="S",
            user_tier="free",
        )

        assert "estimated_cost" in estimate
        assert "mode_comparison" in estimate
        assert estimate["estimated_cost"] == 2.60  # 2 hrs * $1.00 * 1.30 (30% margin for Standard)

    def test_start_and_stop_metering(self):
        """Test full metering workflow."""
        session_id = self.billing.start_job_metering(
            job_id="job_1",
            user_id="user_1",
            provider="gcp",
            instance_type="n1-standard-4",
            region="us-central1",
            provider_hourly_rate=0.50,
            mode="S",
        )

        assert session_id is not None

        time.sleep(0.05)

        usage = self.billing.stop_job_metering(session_id)

        assert usage is not None
        assert usage.total_cost > 0

    def test_charge_for_usage_with_credits(self):
        """Test charging for usage using credits."""
        self.billing.add_user_credits("user_1", 10.00)

        usage = UsageRecord(
            user_id="user_1",
            job_id="job_1",
            total_cost=Decimal("1.50"),
        )

        result = self.billing.charge_for_usage(usage)

        assert result["charged"] is True
        assert result["method"] == "credits"
        assert result["remaining_credits"] == 8.50

    def test_charge_for_usage_insufficient_credits(self):
        """Test charging when credits are insufficient."""
        usage = UsageRecord(
            user_id="user_2",
            job_id="job_2",
            total_cost=Decimal("5.00"),
        )

        result = self.billing.charge_for_usage(usage)

        assert result["charged"] is False
        assert result["reason"] == "insufficient_credits"


class TestInvoiceGenerator:
    """Tests for the InvoiceGenerator class."""

    def setup_method(self):
        """Set up test fixtures."""
        self.generator = InvoiceGenerator()

    def test_generate_invoice_empty(self):
        """Test generating invoice with no usage."""
        invoice = self.generator.generate_invoice(
            user_id="user_1",
            usage_records=[],
        )

        assert invoice.user_id == "user_1"
        assert invoice.subtotal == Decimal("0.00")
        assert invoice.status == "paid"

    def test_generate_invoice_with_usage(self):
        """Test generating invoice with usage records."""
        records = [
            UsageRecord(
                user_id="user_1",
                job_id="job_1",
                provider="gcp",
                instance_type="n1-standard-4",
                compute_seconds=3600,
                total_cost=Decimal("1.30"),
            ),
            UsageRecord(
                user_id="user_1",
                job_id="job_2",
                provider="gcp",
                instance_type="n1-standard-4",
                compute_seconds=1800,
                total_cost=Decimal("0.65"),
            ),
        ]

        invoice = self.generator.generate_invoice(
            user_id="user_1",
            usage_records=records,
        )

        assert invoice.subtotal == Decimal("1.95")
        assert len(invoice.line_items) > 0

    def test_format_invoice(self):
        """Test formatting invoice as text."""
        records = [
            UsageRecord(
                user_id="user_1",
                job_id="job_1",
                provider="gcp",
                instance_type="n1-standard-4",
                compute_seconds=3600,
                total_cost=Decimal("1.30"),
            ),
        ]

        invoice = self.generator.generate_invoice(
            user_id="user_1",
            usage_records=records,
        )

        formatted = self.generator.format_invoice(invoice)

        assert "GATEWAY INVOICE" in formatted
        assert "LINE ITEMS" in formatted
        assert "TOTAL DUE" in formatted


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

    def test_free_tier_exists(self):
        """Test free tier is defined correctly."""
        assert "free" in PRICING_TIERS
        free = PRICING_TIERS["free"]
        assert free.monthly_fee == Decimal("0.00")
        assert free.compute_discount_percent == Decimal("0.00")

    def test_pro_tier_exists(self):
        """Test pro tier is defined correctly."""
        assert "pro" in PRICING_TIERS
        pro = PRICING_TIERS["pro"]
        assert pro.monthly_fee == Decimal("29.00")
        assert pro.compute_discount_percent == Decimal("10.00")

    def test_enterprise_tier_exists(self):
        """Test enterprise tier is defined correctly."""
        assert "enterprise" in PRICING_TIERS
        enterprise = PRICING_TIERS["enterprise"]
        assert enterprise.monthly_fee == Decimal("199.00")
        assert enterprise.compute_discount_percent == Decimal("20.00")


class TestGlobalBillingService:
    """Tests for global billing service functions."""

    def test_get_billing_service(self):
        """Test getting global billing service."""
        service = get_billing_service()
        assert service is not None
        assert isinstance(service, BillingService)

    def test_get_billing_service_singleton(self):
        """Test billing service is a singleton."""
        service1 = get_billing_service()
        service2 = get_billing_service()
        assert service1 is service2

    def test_configure_billing(self):
        """Test configuring billing service."""
        service = configure_billing()
        assert service is not None


class TestModeScenarios:
    """Test real-world pricing scenarios with two-mode system."""

    def setup_method(self):
        """Set up test fixtures."""
        self.engine = PricingEngine()

    def test_scenario_gpu_training_2_hours(self):
        """Test 2-hour GPU training job pricing with percentage margins."""
        provider_rate = 3.50
        duration_hours = 2.0
        duration_seconds = duration_hours * 3600

        performance = self.engine.calculate_price(provider_rate, duration_seconds, "P")
        standard = self.engine.calculate_price(provider_rate, duration_seconds, "S")

        # Performance: 2h * $3.50 * 1.50 = $10.50
        assert abs(performance["net_total"] - 10.50) < 0.01

        # Standard: 2h * $3.50 * 1.30 = $9.10
        assert abs(standard["net_total"] - 9.10) < 0.01

    def test_scenario_quick_inference_5_min(self):
        """Test 5-minute inference job pricing."""
        provider_rate = 1.00
        duration_seconds = 300

        standard = self.engine.calculate_price(provider_rate, duration_seconds, "S")

        # 5 min = 0.0833 hr, cost = 0.0833 * $1.00 * 1.30 = $0.1083 (30% margin)
        assert abs(standard["net_total"] - 0.1083) < 0.01


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