"""
Unit tests for Scanner and Scheduler API views.

Tests endpoint logic with mocked Redis.
"""

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

from app.modules.terminal.scanner.views import deploy_scanner, list_scanners, stop_scanner
from app.modules.terminal.scheduler.views import create_schedule, list_schedules, delete_schedule


# ============================================================================
# Helper to create mock request
# ============================================================================

def make_request(body: dict):
    req = AsyncMock()
    req.json = AsyncMock(return_value=body)
    return req


def make_user(user_id=1):
    user = MagicMock()
    user.id = user_id
    return user


# ============================================================================
# Scanner — Deploy
# ============================================================================


class TestScannerDeploy:
    @pytest.mark.asyncio
    async def test_deploy_kalshi_scanner(self, fake_redis_async):
        user = make_user()
        # Set telegram configured
        await fake_redis_async.set(f"user:{user.id}:telegram", json.dumps({"chat_id": "123"}))

        req = make_request({"platform": "kalshi", "prefix": "KXBTC"})
        result = await deploy_scanner(req, user, fake_redis_async)
        assert result["platform"] == "kalshi"
        assert result["status"] == "running"
        assert result["config"]["prefix"] == "KXBTC"

    @pytest.mark.asyncio
    async def test_deploy_turbine_scanner(self, fake_redis_async):
        user = make_user()
        await fake_redis_async.set(f"user:{user.id}:telegram", json.dumps({"chat_id": "123"}))

        wallets = ["0x" + "ab" * 20]
        req = make_request({"platform": "turbine", "wallets": wallets})
        result = await deploy_scanner(req, user, fake_redis_async)
        assert result["platform"] == "turbine"
        assert result["config"]["wallets"] == wallets

    @pytest.mark.asyncio
    async def test_deploy_invalid_platform(self, fake_redis_async):
        user = make_user()
        req = make_request({"platform": "binance"})
        result = await deploy_scanner(req, user, fake_redis_async)
        assert hasattr(result, "status_code") and result.status_code == 400

    @pytest.mark.asyncio
    async def test_deploy_no_telegram(self, fake_redis_async):
        user = make_user()
        req = make_request({"platform": "kalshi", "prefix": "KXBTC"})
        result = await deploy_scanner(req, user, fake_redis_async)
        assert hasattr(result, "status_code") and result.status_code == 400

    @pytest.mark.asyncio
    async def test_deploy_kalshi_invalid_prefix(self, fake_redis_async):
        user = make_user()
        await fake_redis_async.set(f"user:{user.id}:telegram", json.dumps({"chat_id": "123"}))
        req = make_request({"platform": "kalshi", "prefix": "invalid!@#"})
        result = await deploy_scanner(req, user, fake_redis_async)
        assert hasattr(result, "status_code") and result.status_code == 400

    @pytest.mark.asyncio
    async def test_deploy_turbine_invalid_wallet(self, fake_redis_async):
        user = make_user()
        await fake_redis_async.set(f"user:{user.id}:telegram", json.dumps({"chat_id": "123"}))
        req = make_request({"platform": "turbine", "wallets": ["not-a-wallet"]})
        result = await deploy_scanner(req, user, fake_redis_async)
        assert hasattr(result, "status_code") and result.status_code == 400

    @pytest.mark.asyncio
    async def test_deploy_turbine_no_wallets(self, fake_redis_async):
        user = make_user()
        await fake_redis_async.set(f"user:{user.id}:telegram", json.dumps({"chat_id": "123"}))
        req = make_request({"platform": "turbine", "wallets": []})
        result = await deploy_scanner(req, user, fake_redis_async)
        assert hasattr(result, "status_code") and result.status_code == 400


# ============================================================================
# Scanner — List & Stop
# ============================================================================


class TestScannerListStop:
    @pytest.mark.asyncio
    async def test_list_empty(self, fake_redis_async):
        user = make_user()
        result = await list_scanners(user, fake_redis_async)
        assert result["scanners"] == []

    @pytest.mark.asyncio
    async def test_list_after_deploy(self, fake_redis_async):
        user = make_user()
        await fake_redis_async.set(f"user:{user.id}:telegram", json.dumps({"chat_id": "123"}))
        req = make_request({"platform": "kalshi", "prefix": "KXNBA"})
        scanner = await deploy_scanner(req, user, fake_redis_async)

        result = await list_scanners(user, fake_redis_async)
        assert len(result["scanners"]) == 1
        assert result["scanners"][0]["id"] == scanner["id"]

    @pytest.mark.asyncio
    async def test_stop_scanner(self, fake_redis_async):
        user = make_user()
        await fake_redis_async.set(f"user:{user.id}:telegram", json.dumps({"chat_id": "123"}))
        req = make_request({"platform": "kalshi", "prefix": "KXBTC"})
        scanner = await deploy_scanner(req, user, fake_redis_async)

        result = await stop_scanner(scanner["id"], user, fake_redis_async)
        assert result["ok"] is True

        # Verify removed
        list_result = await list_scanners(user, fake_redis_async)
        assert len(list_result["scanners"]) == 0

    @pytest.mark.asyncio
    async def test_stop_nonexistent_scanner(self, fake_redis_async):
        user = make_user()
        result = await stop_scanner("nonexistent", user, fake_redis_async)
        assert hasattr(result, "status_code") and result.status_code == 404


# ============================================================================
# Scheduler — Create
# ============================================================================


class TestSchedulerCreate:
    @pytest.mark.asyncio
    async def test_create_single_market_schedule(self, fake_redis_async):
        user = make_user()
        start = (datetime.utcnow() + timedelta(hours=1)).isoformat()
        end = (datetime.utcnow() + timedelta(hours=2)).isoformat()

        req = make_request({
            "markets": ["KXNBA-25MAR05-LALMIA-LAL"],
            "start_time": start,
            "end_time": end,
            "config": {"platform": "kalshi"},
        })
        result = await create_schedule(req, user, fake_redis_async)
        assert result["markets"] == ["KXNBA-25MAR05-LALMIA-LAL"]
        assert result["status"] == "pending"

    @pytest.mark.asyncio
    async def test_create_two_market_schedule(self, fake_redis_async):
        user = make_user()
        start = (datetime.utcnow() + timedelta(hours=1)).isoformat()
        end = (datetime.utcnow() + timedelta(hours=2)).isoformat()

        req = make_request({
            "markets": ["KXNBA-25MAR05-LALMIA-LAL", "KXNBA-25MAR05-LALMIA-MIA"],
            "start_time": start,
            "end_time": end,
            "config": {},
        })
        result = await create_schedule(req, user, fake_redis_async)
        assert len(result["markets"]) == 2

    @pytest.mark.asyncio
    async def test_create_no_markets(self, fake_redis_async):
        user = make_user()
        start = (datetime.utcnow() + timedelta(hours=1)).isoformat()
        end = (datetime.utcnow() + timedelta(hours=2)).isoformat()

        req = make_request({
            "markets": [],
            "start_time": start,
            "end_time": end,
        })
        result = await create_schedule(req, user, fake_redis_async)
        assert hasattr(result, "status_code") and result.status_code == 400

    @pytest.mark.asyncio
    async def test_create_too_many_markets(self, fake_redis_async):
        user = make_user()
        start = (datetime.utcnow() + timedelta(hours=1)).isoformat()
        end = (datetime.utcnow() + timedelta(hours=2)).isoformat()

        req = make_request({
            "markets": ["M1-AA-BB", "M2-CC-DD", "M3-EE-FF"],
            "start_time": start,
            "end_time": end,
        })
        result = await create_schedule(req, user, fake_redis_async)
        assert hasattr(result, "status_code") and result.status_code == 400

    @pytest.mark.asyncio
    async def test_create_invalid_ticker(self, fake_redis_async):
        user = make_user()
        start = (datetime.utcnow() + timedelta(hours=1)).isoformat()
        end = (datetime.utcnow() + timedelta(hours=2)).isoformat()

        req = make_request({
            "markets": ["AB"],  # too short
            "start_time": start,
            "end_time": end,
        })
        result = await create_schedule(req, user, fake_redis_async)
        assert hasattr(result, "status_code") and result.status_code == 400

    @pytest.mark.asyncio
    async def test_create_invalid_dates(self, fake_redis_async):
        user = make_user()
        req = make_request({
            "markets": ["KXNBA-25MAR05-YES"],
            "start_time": "not-a-date",
            "end_time": "also-not",
        })
        result = await create_schedule(req, user, fake_redis_async)
        assert hasattr(result, "status_code") and result.status_code == 400

    @pytest.mark.asyncio
    async def test_create_end_before_start(self, fake_redis_async):
        user = make_user()
        start = (datetime.utcnow() + timedelta(hours=2)).isoformat()
        end = (datetime.utcnow() + timedelta(hours=1)).isoformat()

        req = make_request({
            "markets": ["KXNBA-25MAR05-YES"],
            "start_time": start,
            "end_time": end,
        })
        result = await create_schedule(req, user, fake_redis_async)
        assert hasattr(result, "status_code") and result.status_code == 400

    @pytest.mark.asyncio
    async def test_create_window_exceeds_24h(self, fake_redis_async):
        user = make_user()
        start = (datetime.utcnow() + timedelta(hours=1)).isoformat()
        end = (datetime.utcnow() + timedelta(hours=26)).isoformat()

        req = make_request({
            "markets": ["KXNBA-25MAR05-YES"],
            "start_time": start,
            "end_time": end,
        })
        result = await create_schedule(req, user, fake_redis_async)
        assert hasattr(result, "status_code") and result.status_code == 400


# ============================================================================
# Scheduler — List & Delete
# ============================================================================


class TestSchedulerListDelete:
    @pytest.mark.asyncio
    async def test_list_empty(self, fake_redis_async):
        user = make_user()
        result = await list_schedules(user, fake_redis_async)
        assert result["jobs"] == []

    @pytest.mark.asyncio
    async def test_list_after_create(self, fake_redis_async):
        user = make_user()
        start = (datetime.utcnow() + timedelta(hours=1)).isoformat()
        end = (datetime.utcnow() + timedelta(hours=2)).isoformat()
        req = make_request({
            "markets": ["KXNBA-25MAR05-YES"],
            "start_time": start,
            "end_time": end,
            "config": {},
        })
        job = await create_schedule(req, user, fake_redis_async)

        result = await list_schedules(user, fake_redis_async)
        assert len(result["jobs"]) == 1

    @pytest.mark.asyncio
    async def test_delete_schedule(self, fake_redis_async):
        user = make_user()
        start = (datetime.utcnow() + timedelta(hours=1)).isoformat()
        end = (datetime.utcnow() + timedelta(hours=2)).isoformat()
        req = make_request({
            "markets": ["KXNBA-25MAR05-YES"],
            "start_time": start,
            "end_time": end,
            "config": {},
        })
        job = await create_schedule(req, user, fake_redis_async)

        result = await delete_schedule(job["id"], user, fake_redis_async)
        assert result["ok"] is True

        list_result = await list_schedules(user, fake_redis_async)
        assert len(list_result["jobs"]) == 0

    @pytest.mark.asyncio
    async def test_delete_nonexistent(self, fake_redis_async):
        user = make_user()
        result = await delete_schedule("nonexistent", user, fake_redis_async)
        assert hasattr(result, "status_code") and result.status_code == 404
