import uuid
from datetime import datetime, timezone, timedelta
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from app.models.query import Query
from app.models.user import User
from app.services.auth import hash_password
from app.workers.citation_engine import CitationEngine
from app.workers.scheduler import QueryScheduler


# ---------------------------------------------------------------------------
# 1. 调度器启动 / 关闭
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_scheduler_start_stop():
    scheduler = QueryScheduler()
    scheduler.engine = AsyncMock()

    scheduler.start()
    # Verify the scheduled job was added
    job = scheduler.scheduler.get_job("check_queries")
    assert job is not None
    assert job.name == "检查并执行到期的查询任务"

    await scheduler.shutdown()
    # Verify engine.close was awaited
    scheduler.engine.close.assert_awaited_once()


# ---------------------------------------------------------------------------
# 2. 查询任务筛选：只选择 status=active 且 next_query_at <= now 的查询
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_scheduler_query_filtering(test_session):
    # Create a user first
    user = User(
        email="sched@test.com",
        password_hash=hash_password("pass"),
        name="Scheduler",
    )
    test_session.add(user)
    await test_session.commit()
    await test_session.refresh(user)

    now = datetime.now(timezone.utc)

    # q1: active and overdue -> should be picked
    q1 = Query(
        user_id=user.id,
        keyword="overdue",
        target_brand="B1",
        status="active",
        next_query_at=now - timedelta(hours=1),
    )
    # q2: active but in the future -> should NOT be picked
    q2 = Query(
        user_id=user.id,
        keyword="future",
        target_brand="B2",
        status="active",
        next_query_at=now + timedelta(days=1),
    )
    # q3: paused and overdue -> should NOT be picked
    q3 = Query(
        user_id=user.id,
        keyword="paused",
        target_brand="B3",
        status="paused",
        next_query_at=now - timedelta(hours=1),
    )

    test_session.add_all([q1, q2, q3])
    await test_session.commit()

    scheduler = QueryScheduler()
    scheduler.engine = AsyncMock()

    # Mock AsyncSessionLocal so scheduler uses our test session
    mock_local = MagicMock()
    mock_local.return_value.__aenter__ = AsyncMock(return_value=test_session)
    mock_local.return_value.__aexit__ = AsyncMock(return_value=False)

    with patch("app.workers.scheduler.AsyncSessionLocal", mock_local):
        await scheduler.check_and_execute_queries()

    # execute_query should be called exactly once (for q1)
    scheduler.engine.execute_query.assert_called_once()
    called_query = scheduler.engine.execute_query.call_args[0][0]
    assert called_query.keyword == "overdue"


# ---------------------------------------------------------------------------
# 3. 频率计算：daily 和 weekly 的 next_query_at 正确计算
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_scheduler_frequency_calculation_daily():
    engine = CitationEngine()
    now = datetime.utcnow()
    result = engine._calculate_next_query_at("daily")
    expected = now + timedelta(days=1)
    assert abs((result - expected).total_seconds()) < 5


@pytest.mark.asyncio
async def test_scheduler_frequency_calculation_weekly():
    engine = CitationEngine()
    now = datetime.utcnow()
    result = engine._calculate_next_query_at("weekly")
    expected = now + timedelta(days=7)
    assert abs((result - expected).total_seconds()) < 5


@pytest.mark.asyncio
async def test_scheduler_frequency_calculation_default():
    engine = CitationEngine()
    now = datetime.utcnow()
    result = engine._calculate_next_query_at(None)
    expected = now + timedelta(days=7)
    assert abs((result - expected).total_seconds()) < 5
