"""
Integration tests for FastAPI API endpoints.

Builds a lightweight test FastAPI app using the real routers,
with mocked DB, Redis, and auth dependencies.
Skipped when the agentic module is not available (e.g. CI without full repo).
"""

import json
import pytest
from unittest.mock import AsyncMock, MagicMock, patch

pytest.importorskip("agentic", reason="agentic module not available in this environment")

from fastapi import FastAPI
from fastapi.testclient import TestClient

from app.core.api import api_router
from app.dependencies import get_db, get_redis, get_current_user


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def mock_redis_for_app():
    redis = AsyncMock()
    redis.get = AsyncMock(return_value=None)
    redis.set = AsyncMock()
    redis.setex = AsyncMock()
    redis.delete = AsyncMock()
    redis.rpush = AsyncMock()
    redis.lpop = AsyncMock(return_value=None)
    redis.sadd = AsyncMock()
    redis.smembers = AsyncMock(return_value=set())
    redis.zadd = AsyncMock()
    redis.zrange = AsyncMock(return_value=[])
    redis.zrem = AsyncMock()
    redis.srem = AsyncMock()
    return redis


@pytest.fixture
def test_user_obj():
    from app.modules.user.models import User
    return User(
        id=1,
        username="testtrader",
        email="trader@test.com",
        hashed_password="$2b$12$hashed",
        is_active=True,
        is_superuser=False,
        failed_login_attempts=0,
        recent_events=[{"market": "KXBTC", "time": "2025-03-05"}],
    )


@pytest.fixture
def test_app(mock_redis_for_app, test_user_obj):
    """Build a clean FastAPI test app with the real routers and mocked deps."""
    app = FastAPI()
    # Register AppException handler for proper error codes, but skip validation handler
    # (the default FastAPI validation handler works fine for tests)
    from app.core.exceptions import AppException
    from fastapi.responses import JSONResponse
    @app.exception_handler(AppException)
    async def app_exc_handler(request, exc):
        return JSONResponse(status_code=exc.status_code, content={"detail": exc.message})
    app.include_router(api_router, prefix="/api/v1")
    # Patch get_user_identifier so rate limiter doesn't fail on missing request.state.user
    _patcher = patch("app.middleware.rate_limiter.get_user_identifier", return_value="user_test")
    _patcher.start()

    mock_db = AsyncMock()
    mock_db.add = MagicMock()
    mock_db.commit = AsyncMock()
    mock_db.refresh = AsyncMock()
    mock_db.delete = AsyncMock()
    mock_db.execute = AsyncMock()

    async def override_db():
        yield mock_db

    async def override_redis():
        yield mock_redis_for_app

    async def override_current_user():
        return test_user_obj

    app.dependency_overrides[get_db] = override_db
    app.dependency_overrides[get_redis] = override_redis
    app.dependency_overrides[get_current_user] = override_current_user

    yield app, mock_db, mock_redis_for_app, test_user_obj
    _patcher.stop()


@pytest.fixture
def client(test_app):
    app, _, _, _ = test_app
    return TestClient(app, raise_server_exceptions=False)


# ============================================================================
# Recent Events
# ============================================================================


class TestRecentEvents:
    def test_get_recent_events(self, client):
        resp = client.get("/api/v1/terminal/auto/recent-events")
        assert resp.status_code == 200
        data = resp.json()
        assert "recent_events" in data
        assert len(data["recent_events"]) == 1


# ============================================================================
# Instance Listing
# ============================================================================


class TestInstanceListing:
    def test_list_instances_empty(self, test_app):
        app, mock_db, _, _ = test_app
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = []
        mock_db.execute = AsyncMock(return_value=mock_result)

        with TestClient(app, raise_server_exceptions=False) as c:
            resp = c.get("/api/v1/terminal/auto/instances")
            assert resp.status_code == 200
            data = resp.json()
            assert "instances" in data
            assert data["total"] == 0


# ============================================================================
# Deploy Validation
# ============================================================================


class TestDeployValidation:
    def test_deploy_missing_fields(self, client):
        resp = client.post(
            "/api/v1/terminal/auto/deploy",
            json={"platform": "kalshi"},
        )
        assert resp.status_code == 422

    def test_deploy_invalid_platform(self, client):
        resp = client.post(
            "/api/v1/terminal/auto/deploy",
            json={
                "platform": "binance",
                "num_markets": 1,
                "mode": "automated",
                "markets": ["TICKER-XX-YY"],
            },
        )
        assert resp.status_code == 422


# ============================================================================
# Control Actions
# ============================================================================


class TestControlActions:
    def test_control_invalid_action(self, client):
        resp = client.post(
            "/api/v1/terminal/auto/instances/1/control",
            json={"action": "explode"},
        )
        assert resp.status_code == 422

    def test_control_valid_pause(self, test_app):
        from app.modules.terminal.auto.models import TradingInstance, InstanceStatus, ScriptType
        app, mock_db, mock_redis, _ = test_app

        inst = MagicMock(spec=TradingInstance)
        inst.id = 1
        inst.user_id = 1
        inst.status = InstanceStatus.RUNNING
        inst.script = ScriptType.AUTO1
        inst.markets = {"markets": ["M1-AA-BB"]}
        inst.config = {"grid_mode": False}
        inst.position = 0
        inst.pnl = 0.0
        inst.start_time = "2025-03-05 10:00:00"
        inst.celery_task_id = "abc"
        inst.orderbook_data = None
        inst.current_increment = None

        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = inst
        mock_db.execute = AsyncMock(return_value=mock_result)

        with TestClient(app, raise_server_exceptions=False) as c:
            resp = c.post(
                "/api/v1/terminal/auto/instances/1/control",
                json={"action": "toggle_pause"},
            )
            assert resp.status_code == 200, f"Failed: {resp.text}"


# ============================================================================
# Delete Instance
# ============================================================================


class TestDeleteInstance:
    def test_delete_running_instance_rejected(self, test_app):
        from app.modules.terminal.auto.models import TradingInstance, InstanceStatus

        app, mock_db, _, _ = test_app
        inst = MagicMock(spec=TradingInstance)
        inst.id = 1
        inst.user_id = 1
        inst.status = InstanceStatus.RUNNING

        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = inst
        mock_db.execute = AsyncMock(return_value=mock_result)

        with TestClient(app, raise_server_exceptions=False) as c:
            resp = c.delete("/api/v1/terminal/auto/instances/1")
            assert resp.status_code == 400


# ============================================================================
# Scanner Endpoints
# ============================================================================


class TestScannerEndpoints:
    def test_list_scanners_empty(self, test_app):
        app, _, mock_redis, _ = test_app
        mock_redis.smembers = AsyncMock(return_value=set())

        with TestClient(app, raise_server_exceptions=False) as c:
            resp = c.get("/api/v1/terminal/scanner/list")
            assert resp.status_code == 200
            assert resp.json()["scanners"] == []

    def test_deploy_scanner_no_telegram(self, test_app):
        app, _, mock_redis, _ = test_app
        mock_redis.get = AsyncMock(return_value=None)

        with TestClient(app, raise_server_exceptions=False) as c:
            resp = c.post(
                "/api/v1/terminal/scanner/deploy",
                json={"platform": "kalshi", "prefix": "KXBTC"},
            )
            assert resp.status_code == 400


# ============================================================================
# Scheduler Endpoints
# ============================================================================


class TestSchedulerEndpoints:
    def test_list_schedules_empty(self, test_app):
        app, _, mock_redis, _ = test_app
        mock_redis.smembers = AsyncMock(return_value=set())

        with TestClient(app, raise_server_exceptions=False) as c:
            resp = c.get("/api/v1/terminal/scheduler/list")
            assert resp.status_code == 200
            assert resp.json()["jobs"] == []

    def test_create_schedule_no_markets(self, test_app):
        app, _, _, _ = test_app
        with TestClient(app, raise_server_exceptions=False) as c:
            resp = c.post(
                "/api/v1/terminal/scheduler/create",
                json={
                    "markets": [],
                    "start_time": "2025-03-05T18:00",
                    "end_time": "2025-03-05T19:00",
                },
            )
            assert resp.status_code == 400

    def test_delete_nonexistent_schedule(self, test_app):
        app, _, mock_redis, _ = test_app
        mock_redis.get = AsyncMock(return_value=None)

        with TestClient(app, raise_server_exceptions=False) as c:
            resp = c.delete("/api/v1/terminal/scheduler/fake-id")
            assert resp.status_code == 404


# ============================================================================
# Logout
# ============================================================================


class TestLogout:
    def test_logout_clears_credentials(self, test_app):
        app, _, mock_redis, _ = test_app
        with TestClient(app, raise_server_exceptions=False) as c:
            resp = c.post(
                "/api/v1/terminal/auto/logout",
                headers={"Authorization": "Bearer test-token-abc123"},
            )
            assert resp.status_code == 200
            mock_redis.delete.assert_called()
