"""Shared test fixtures."""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from fastapi.testclient import TestClient

from proxym.config import Settings, get_settings, _settings
from proxym.main import app
from proxym.providers import MODELS
from proxym.router.strategy import CostLedger, Router


@pytest.fixture(autouse=True)
def mock_litellm_in_tests():
    """Mock litellm.acompletion to prevent real API calls in tests."""
    mock_resp = AsyncMock()
    mock_resp.choices = [AsyncMock(message=AsyncMock(content="Mocked LLM response"))]
    mock_resp.usage = AsyncMock(prompt_tokens=10, completion_tokens=5)
    mock_resp.model = "openrouter/anthropic/claude-sonnet-4-20250514"
    
    with patch("litellm.acompletion", new_callable=AsyncMock, return_value=mock_resp):
        yield


@pytest.fixture(autouse=True)
def clear_settings_cache():
    """Clear settings cache and dashboard singletons before each test."""
    import proxym.config
    proxym.config._settings = None
    # Reset dashboard singletons to prevent state leaking between test files
    import proxym.dashboard._tickets_api as tickets_mod
    import proxym.dashboard._accounts as accounts_mod
    import proxym.dashboard._costs as costs_mod
    import proxym.dashboard._system as system_mod
    import proxym.dashboard._vms as vms_mod
    import proxym.dashboard._projects_api as projects_mod
    from proxym.dashboard.tickets import TicketManager
    from proxym.accounts import AccountManager
    from proxym.projects import ProjectRegistry
    from proxym.virt import VMOrchestrator
    _empty_acct = AccountManager()
    tickets_mod._ticket_mgr = TicketManager()
    accounts_mod._acct_mgr = _empty_acct
    costs_mod._acct_mgr = _empty_acct
    system_mod._acct_mgr = _empty_acct
    vms_mod._vm_orch = VMOrchestrator()
    projects_mod._registry = ProjectRegistry()
    
    # Reset ToolExecutor singleton and stop any running worker
    from proxym.tools.executor import ToolExecutor
    executor = ToolExecutor._instance
    if executor:
        executor._running = False
        executor._jobs = {}
        # Don't try to cancel tasks across event loops - just reset the instance
        executor._worker_task = None
        try:
            executor._queue = asyncio.Queue(maxsize=100)
        except Exception:
            pass
    ToolExecutor._instance = None
    
    yield
    tickets_mod._ticket_mgr = None
    accounts_mod._acct_mgr = None
    costs_mod._acct_mgr = None
    system_mod._acct_mgr = None
    vms_mod._vm_orch = None
    projects_mod._registry = None
    ToolExecutor._instance = None


@pytest.fixture
def settings():
    return Settings(
        AI_PROXY_HOST="127.0.0.1",
        AI_PROXY_PORT=4000,
        AI_PROXY_MASTER_KEY="sk-test",
        ANTHROPIC_API_KEY="sk-ant-test",
        OPENAI_API_KEY="sk-test",
        OPENROUTER_API_KEY="sk-or-test",
        GOOGLE_AI_KEY="AI-test",
        DEEPSEEK_API_KEY="sk-ds-test",
        GROQ_API_KEY="gsk-test",
        REDIS_URL="redis://localhost:6379/0",
        MONTHLY_BUDGET_USD=60.0,
        DAILY_BUDGET_USD=5.0,
        MAX_REQUEST_COST_USD=2.0,
    )


@pytest.fixture
def all_providers():
    """All providers available for routing."""
    return {
        "anthropic", "openai", "openrouter", "google",
        "deepseek", "groq", "together", "mistral",
        "fireworks", "cerebras", "ollama",
    }


@pytest.fixture
def router(all_providers):
    ledger = CostLedger(daily_limit=5.0, monthly_limit=60.0)
    return Router(
        available_providers=all_providers,
        ledger=ledger,
        max_request_cost=2.0,
    )


@pytest.fixture
def cheap_router():
    """Router with only free/cheap providers."""
    providers = {"ollama", "deepseek", "groq", "cerebras", "google"}
    ledger = CostLedger(daily_limit=1.0, monthly_limit=10.0)
    return Router(
        available_providers=providers,
        ledger=ledger,
        max_request_cost=0.5,
    )


@pytest.fixture
def tmp_project(tmp_path):
    """Create a temporary project directory with sample files."""
    src = tmp_path / "src"
    src.mkdir()
    (src / "main.py").write_text("def main():\n    print('hello')\n")
    (src / "utils.py").write_text("def add(a, b):\n    return a + b\n")
    (tmp_path / "README.md").write_text("# Test Project\n")
    (tmp_path / "pyproject.toml").write_text('[project]\nname = "test"\n')
    return tmp_path


# ─── App fixtures ───────────────────────────────────────────

@pytest.fixture
def client():
    """FastAPI TestClient with default settings."""
    # Clear settings cache to ensure fresh config from env
    from proxym.config import _settings
    import proxym.config
    proxym.config._settings = None
    with TestClient(app) as c:
        yield c


@pytest.fixture
def authed_client():
    """Client with authorization header (separate instance)."""
    import proxym.config
    proxym.config._settings = None
    with TestClient(app) as c:
        c.headers["Authorization"] = "Bearer sk-proxy-local-dev"
        yield c


# ─── Account fixtures ────────────────────────────────────────

@pytest.fixture
def empty_account_manager(tmp_path):
    """Empty account manager with temp config dir."""
    from proxym.accounts import AccountManager
    return AccountManager(config_dir=tmp_path)


@pytest.fixture
def account_manager_with_anthropic(empty_account_manager):
    """Account manager with test anthropic account."""
    from proxym.accounts import Account, ProviderType, AccountCredentials, AccountLimits
    acct = Account(
        name="Test Anthropic",
        provider=ProviderType.ANTHROPIC,
        credentials=AccountCredentials(api_key="sk-ant-test-key"),
        limits=AccountLimits(user_daily_budget_usd=5.0, user_monthly_budget_usd=60.0),
        tags=["test"],
    )
    empty_account_manager.add_account(acct)
    return empty_account_manager


# ─── Router fixtures ─────────────────────────────────────────

@pytest.fixture
def mock_litellm_response():
    """Mock response from litellm."""
    return MagicMock(
        choices=[MagicMock(message=MagicMock(content="Test response"))],
        usage=MagicMock(prompt_tokens=100, completion_tokens=50),
    )


@pytest.fixture
def router_with_mock_completion(mock_litellm_response):
    """Router with mocked completion call."""
    ledger = CostLedger(daily_limit=5.0, monthly_limit=60.0)
    router = Router(
        available_providers={"anthropic", "openrouter"},
        ledger=ledger,
        max_request_cost=2.0,
    )
    # Mock the completion method
    router._call_completion = AsyncMock(return_value=mock_litellm_response)
    return router


# ─── VM fixtures ─────────────────────────────────────────────

@pytest.fixture
def mock_clonebox():
    """Mock clonebox subprocess calls."""
    with patch("proxym.virt.clonebox_adapter.subprocess.run") as m:
        m.return_value = MagicMock(returncode=0, stdout="ok", stderr="")
        yield m


@pytest.fixture
def mock_libvirt():
    """Mock libvirt subprocess calls."""
    with patch("proxym.virt._orchestrator.subprocess.run") as m:
        m.return_value = MagicMock(returncode=0, stdout="", stderr="")
        yield m


# ─── MCP fixtures ────────────────────────────────────────────

@pytest.fixture
def mock_mcp_registry():
    """Mock MCP server registry."""
    return {
        "github": {"url": "http://localhost:3001/sse", "enabled": True},
        "filesystem": {"url": "http://localhost:3002/sse", "enabled": True},
    }


# ─── Delta fixtures ──────────────────────────────────────────

@pytest.fixture
def valid_delta_payload():
    """Valid delta payload for context updates."""
    return {
        "project_id": "test-project",
        "version": 1,
        "changed_files": ["src/main.py"],
        "diff_text": "--- a/src/main.py\n+++ b/src/main.py\n@@ -1 +1 @@\n-old\n+new",
        "token_estimate": 150,
    }


@pytest.fixture
def delta_context():
    """Delta context buffer."""
    from proxym.context import DeltaContextBuffer
    return DeltaContextBuffer(max_tokens=100000)


# ─── Context fixtures ─────────────────────────────────────────

@pytest.fixture
def empty_context_store():
    """Empty context store."""
    from proxym.context import ContextStore
    return ContextStore()


@pytest.fixture
def session_store(tmp_path):
    """Session store with temp directory."""
    from proxym.context.session import SessionStore
    return SessionStore(base_dir=tmp_path / "sessions")


# ─── Error fixtures ───────────────────────────────────────────

@pytest.fixture
def mock_429_response():
    """Mock rate limit (429) response."""
    from httpx import HTTPStatusError, Response
    response = MagicMock(spec=Response)
    response.status_code = 429
    response.text = "Rate limit exceeded"
    return HTTPStatusError("Rate limit", request=MagicMock(), response=response)


@pytest.fixture
def mock_500_response():
    """Mock server error (500) response."""
    from httpx import HTTPStatusError, Response
    response = MagicMock(spec=Response)
    response.status_code = 500
    response.text = "Internal server error"
    return HTTPStatusError("Server error", request=MagicMock(), response=response)


# ─── Helpers ────────────────────────────────────────────────

@pytest.fixture
def assert_cost_header():
    """Helper to check cost header in response."""
    def _assert(response):
        assert "X-Proxym-Cost" in response.headers
        cost = float(response.headers["X-Proxym-Cost"])
        assert cost >= 0
    return _assert


@pytest.fixture
def assert_json_response():
    """Helper to check JSON response structure."""
    def _assert(response, expected_keys=None):
        assert response.status_code == 200
        data = response.json()
        if expected_keys:
            for key in expected_keys:
                assert key in data
        return data
    return _assert
