"""Tests for BudgetManager — centralized budget with $500/mo default."""

from __future__ import annotations

import json
import tempfile
from pathlib import Path

import pytest

from orxaq.orchestration.providers.cost import BudgetManager


class TestBudgetManagerDefaults:
    def test_default_monthly_budget(self) -> None:
        mgr = BudgetManager()
        assert mgr.monthly_budget_usd == 500.0

    def test_custom_monthly_budget(self) -> None:
        mgr = BudgetManager(monthly_budget_usd=1000.0)
        assert mgr.monthly_budget_usd == 1000.0

    def test_tracker_has_correct_hard_limit(self) -> None:
        mgr = BudgetManager(monthly_budget_usd=250.0)
        assert mgr.tracker._budget.hard_limit_usd == 250.0

    def test_tracker_soft_limit_is_80_percent(self) -> None:
        mgr = BudgetManager(monthly_budget_usd=500.0)
        assert mgr.tracker._budget.soft_limit_usd == pytest.approx(400.0)

    def test_tracker_period_is_30_days(self) -> None:
        mgr = BudgetManager()
        assert mgr.tracker._budget.period_seconds == 30 * 24 * 3600


class TestBudgetManagerRecord:
    def test_record_returns_usage_record(self) -> None:
        mgr = BudgetManager()
        rec = mgr.record("openai", "gpt-4", 100, 50, latency_ms=100.0)
        assert rec.provider == "openai"
        assert rec.input_tokens == 100
        assert rec.cost_usd > 0

    def test_record_accumulates(self) -> None:
        mgr = BudgetManager()
        mgr.record("openai", "gpt-4", 100, 50)
        mgr.record("anthropic", "claude-3-sonnet", 200, 100)
        assert len(mgr.tracker.records) == 2


class TestBudgetManagerStatus:
    def test_status_keys(self) -> None:
        mgr = BudgetManager()
        st = mgr.status()
        assert "monthly_budget_usd" in st
        assert "spent_usd" in st
        assert "remaining_usd" in st
        assert "within_budget" in st
        assert "warning" in st
        assert "daily_budget_usd" in st
        assert "total_calls" in st
        assert "by_provider" in st

    def test_status_initial_values(self) -> None:
        mgr = BudgetManager()
        st = mgr.status()
        assert st["monthly_budget_usd"] == 500.0
        assert st["spent_usd"] == 0.0
        assert st["remaining_usd"] == 500.0
        assert st["within_budget"] is True
        assert st["warning"] is False
        assert st["total_calls"] == 0

    def test_status_after_recording(self) -> None:
        mgr = BudgetManager()
        mgr.record("openai", "gpt-4", 1000, 500)
        st = mgr.status()
        assert st["spent_usd"] > 0
        assert st["remaining_usd"] < 500.0
        assert st["total_calls"] == 1
        assert "openai" in st["by_provider"]

    def test_daily_budget_calculation(self) -> None:
        mgr = BudgetManager(monthly_budget_usd=300.0)
        st = mgr.status()
        assert st["daily_budget_usd"] == pytest.approx(10.0)


class TestBudgetManagerSetBudget:
    def test_set_budget_updates_value(self) -> None:
        mgr = BudgetManager()
        mgr.set_budget(1000.0)
        assert mgr.monthly_budget_usd == 1000.0

    def test_set_budget_updates_tracker_limits(self) -> None:
        mgr = BudgetManager()
        mgr.set_budget(200.0)
        assert mgr.tracker._budget.hard_limit_usd == 200.0
        assert mgr.tracker._budget.soft_limit_usd == pytest.approx(160.0)


class TestBudgetManagerPersistence:
    def test_save_and_load_config(self) -> None:
        with tempfile.TemporaryDirectory() as tmp:
            config_path = Path(tmp) / "budget.json"
            mgr = BudgetManager(config_path=config_path, monthly_budget_usd=750.0)
            mgr.set_budget(750.0)  # triggers save
            assert config_path.exists()
            data = json.loads(config_path.read_text())
            assert data["monthly_budget_usd"] == 750.0

    def test_load_persisted_budget(self) -> None:
        with tempfile.TemporaryDirectory() as tmp:
            config_path = Path(tmp) / "budget.json"
            config_path.write_text(json.dumps({"monthly_budget_usd": 999.0}))
            mgr = BudgetManager(config_path=config_path)
            assert mgr.monthly_budget_usd == 999.0
            assert mgr.tracker._budget.hard_limit_usd == 999.0

    def test_no_config_path_no_error(self) -> None:
        mgr = BudgetManager()
        mgr.set_budget(100.0)  # should not raise
        assert mgr.monthly_budget_usd == 100.0

    def test_nonexistent_config_path_uses_default(self) -> None:
        mgr = BudgetManager(config_path="/tmp/nonexistent_dir_xyz/budget.json")
        assert mgr.monthly_budget_usd == 500.0


class TestBudgetManagerBudgetEnforcement:
    def test_within_budget_after_small_spend(self) -> None:
        mgr = BudgetManager(monthly_budget_usd=100.0)
        mgr.record("vllm", "default", 10000, 5000)  # $0 cost
        assert mgr.tracker.is_within_budget() is True

    def test_over_budget_detected(self) -> None:
        mgr = BudgetManager(monthly_budget_usd=0.01)
        # gpt-4: $0.03 input + $0.06 output per 1K → $0.09 per 1K combined
        for _ in range(5):
            mgr.record("openai", "gpt-4", 5000, 5000)
        assert mgr.tracker.is_within_budget() is False

    def test_warning_at_80_percent(self) -> None:
        mgr = BudgetManager(monthly_budget_usd=1.0)
        # Spend enough to pass 80% ($0.80) but stay under $1.00
        # gpt-4: $0.09 per 1K tokens combined → need ~9K combined tokens for $0.81
        for _ in range(10):
            mgr.record("openai", "gpt-4", 500, 500)
        st = mgr.status()
        # Check that we're in warning territory if spent > 0.80
        if st["spent_usd"] >= 0.80:
            assert st["warning"] is True
