import sys
from datetime import UTC, date, datetime
from unittest.mock import Mock

import pytest
from fastapi import FastAPI
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine

sys.modules.setdefault('psutil', Mock())

import app.models  # noqa: F401
from app.database import Base, get_db
from app.models.goal import Goal
from app.models.reminder import Reminder
from app.models.task import Task, TaskPriority, TaskStatus
from app.models.todo import DailyTodo, TodoSource
from app.models.user import User
from app.routers.auth import get_current_user
from app.routers.goal import router as goal_router
from app.routers.reminder import router as reminder_router
from app.routers.schedule_center import router as schedule_center_router
from app.routers.task import router as task_router
from app.routers.todo import router as todo_router
from app.services.auth_service import get_password_hash


@pytest.fixture
async def schedule_env(tmp_path):
    db_path = tmp_path / 'test_schedule_center.db'
    engine = create_async_engine(f"sqlite+aiosqlite:///{db_path}", future=True)
    session_factory = async_sessionmaker(engine, expire_on_commit=False)

    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

    async with session_factory() as session:
        user = User(
            username='schedule_user',
            email='schedule@example.com',
            hashed_password=get_password_hash('secret123'),
            full_name='Schedule Tester',
        )
        other_user = User(
            username='other_schedule_user',
            email='other-schedule@example.com',
            hashed_password=get_password_hash('secret123'),
            full_name='Other Schedule Tester',
        )
        session.add_all([user, other_user])
        await session.flush()

        session.add_all([
            DailyTodo(
                user_id=user.id,
                title='Legacy todo',
                source=TodoSource.MANUAL,
                todo_date='2026-04-10',
                is_completed=False,
            ),
            DailyTodo(
                user_id=user.id,
                title='Done todo',
                source=TodoSource.MANUAL,
                todo_date='2026-04-10',
                is_completed=True,
                completed_at=datetime(2026, 4, 10, 9, 30, tzinfo=UTC),
            ),
            DailyTodo(
                user_id=other_user.id,
                title='Other user todo',
                source=TodoSource.MANUAL,
                todo_date='2026-04-10',
                is_completed=False,
            ),
            Task(
                user_id=user.id,
                title='High priority task',
                priority=TaskPriority.HIGH,
                status=TaskStatus.TODO,
                due_date=datetime(2026, 4, 10, 14, 0, tzinfo=UTC),
            ),
            Task(
                user_id=user.id,
                title='Urgent task next day',
                priority=TaskPriority.URGENT,
                status=TaskStatus.IN_PROGRESS,
                due_date=datetime(2026, 4, 11, 10, 0, tzinfo=UTC),
            ),
            Task(
                user_id=other_user.id,
                title='Other user task',
                priority=TaskPriority.HIGH,
                status=TaskStatus.TODO,
                due_date=datetime(2026, 4, 10, 15, 0, tzinfo=UTC),
            ),
            Reminder(
                user_id=user.id,
                title='Doctor reminder',
                note='Bring reports',
                reminder_at=datetime(2026, 4, 10, 8, 0, tzinfo=UTC),
            ),
            Goal(
                user_id=user.id,
                title='Launch calendar beta',
                note='Ship MVP',
                goal_date='2026-04-10',
            ),
        ])
        await session.commit()
        await session.refresh(user)

    async def override_get_db():
        async with session_factory() as session:
            yield session

    async def override_get_current_user():
        return user

    test_app = FastAPI()
    test_app.include_router(todo_router)
    test_app.include_router(task_router)
    test_app.include_router(reminder_router)
    test_app.include_router(goal_router)
    test_app.include_router(schedule_center_router)
    test_app.dependency_overrides[get_db] = override_get_db
    test_app.dependency_overrides[get_current_user] = override_get_current_user

    try:
        yield test_app
    finally:
        await engine.dispose()


@pytest.mark.asyncio
async def test_create_todo_persists_explicit_todo_date(schedule_env):
    transport = ASGITransport(app=schedule_env)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.post('/api/todos', json={'title': 'Plan sprint', 'todo_date': '2026-04-12'})

    assert response.status_code == 201
    payload = response.json()
    assert payload['title'] == 'Plan sprint'
    assert payload['todo_date'] == '2026-04-12'


@pytest.mark.asyncio
async def test_update_todo_allows_editing_non_today_todo(schedule_env):
    transport = ASGITransport(app=schedule_env)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        todos_response = await client.get('/api/todos', params={'date_str': '2026-04-10'})
        todo_id = todos_response.json()['items'][0]['id']
        response = await client.patch(f'/api/todos/{todo_id}', json={'title': 'Updated title', 'todo_date': '2026-04-11'})

    assert response.status_code == 200
    payload = response.json()
    assert payload['title'] == 'Updated title'
    assert payload['todo_date'] == '2026-04-11'


@pytest.mark.asyncio
async def test_delete_todo_allows_deleting_non_today_todo(schedule_env):
    transport = ASGITransport(app=schedule_env)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        todos_response = await client.get('/api/todos', params={'date_str': '2026-04-10'})
        todo_id = todos_response.json()['items'][0]['id']
        response = await client.delete(f'/api/todos/{todo_id}')
        after_response = await client.get('/api/todos', params={'date_str': '2026-04-10'})

    assert response.status_code == 204
    assert all(item['id'] != todo_id for item in after_response.json()['items'])


@pytest.mark.asyncio
async def test_list_tasks_filters_by_due_date(schedule_env):
    transport = ASGITransport(app=schedule_env)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.get('/api/tasks', params={'due_date': '2026-04-10'})

    assert response.status_code == 200
    payload = response.json()
    assert [item['title'] for item in payload] == ['High priority task']


@pytest.mark.asyncio
async def test_list_tasks_filters_by_due_date_range(schedule_env):
    transport = ASGITransport(app=schedule_env)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.get('/api/tasks', params={'date_from': '2026-04-10', 'date_to': '2026-04-11'})

    assert response.status_code == 200
    payload = response.json()
    assert {item['title'] for item in payload} == {'High priority task', 'Urgent task next day'}


@pytest.mark.asyncio
async def test_get_schedule_center_date_returns_aggregated_resources(schedule_env):
    transport = ASGITransport(app=schedule_env)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.get('/api/schedule-center/date', params={'date_str': '2026-04-10'})

    assert response.status_code == 200
    payload = response.json()
    assert payload['date'] == '2026-04-10'
    assert payload['summary'] == {
        'date': '2026-04-10',
        'todo_total': 2,
        'todo_completed': 1,
        'task_due_total': 1,
        'high_priority_total': 1,
        'reminder_total': 1,
        'goal_total': 1,
    }
    assert [item['title'] for item in payload['reminders']] == ['Doctor reminder']
    assert [item['title'] for item in payload['goals']] == ['Launch calendar beta']


@pytest.mark.asyncio
async def test_get_schedule_center_month_returns_day_summaries(schedule_env):
    transport = ASGITransport(app=schedule_env)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.get('/api/schedule-center/month', params={'year': 2026, 'month': 4})

    assert response.status_code == 200
    payload = response.json()
    assert payload['month'] == '2026-04'
    day_10 = next(item for item in payload['days'] if item['date'] == '2026-04-10')
    day_11 = next(item for item in payload['days'] if item['date'] == '2026-04-11')
    assert day_10 == {
        'date': '2026-04-10',
        'todo_total': 2,
        'todo_completed': 1,
        'task_due_total': 1,
        'high_priority_total': 1,
        'reminder_total': 1,
        'goal_total': 1,
    }
    assert day_11['task_due_total'] == 1
    assert day_11['high_priority_total'] == 1


@pytest.mark.asyncio
async def test_create_reminder_with_naive_datetime_and_time_zone_appears_in_schedule_center(schedule_env):
    transport = ASGITransport(app=schedule_env)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        create_response = await client.post(
            '/api/reminders',
            json={'title': '收被子', 'note': '提醒收被子', 'reminder_at': '2026-03-29T09:00:00'},
        )
        detail_response = await client.get('/api/schedule-center/date', params={'date_str': '2026-03-29'})

    assert create_response.status_code == 201
    assert detail_response.status_code == 200
    payload = detail_response.json()
    assert [item['title'] for item in payload['reminders']] == ['收被子']
    assert payload['summary']['reminder_total'] == 1


@pytest.mark.asyncio
async def test_reminder_and_goal_crud(schedule_env):
    transport = ASGITransport(app=schedule_env)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        reminder_response = await client.post('/api/reminders', json={'title': 'Standup', 'note': 'Daily sync', 'reminder_at': '2026-04-12T09:00:00Z'})
        goal_response = await client.post('/api/goals', json={'title': 'Finish polish', 'note': 'UI cleanup', 'goal_date': '2026-04-12', 'status': 'active'})
        reminder_id = reminder_response.json()['id']
        goal_id = goal_response.json()['id']
        patch_reminder = await client.patch(f'/api/reminders/{reminder_id}', json={'status': 'done'})
        patch_goal = await client.patch(f'/api/goals/{goal_id}', json={'status': 'done'})
        reminders_list = await client.get('/api/reminders', params={'date_str': '2026-04-12'})
        goals_list = await client.get('/api/goals', params={'date_str': '2026-04-12'})
        delete_reminder = await client.delete(f'/api/reminders/{reminder_id}')
        delete_goal = await client.delete(f'/api/goals/{goal_id}')

    assert reminder_response.status_code == 201
    assert goal_response.status_code == 201
    assert patch_reminder.json()['status'] == 'done'
    assert patch_goal.json()['status'] == 'done'
    assert [item['title'] for item in reminders_list.json()['items']] == ['Standup']
    assert [item['title'] for item in goals_list.json()['items']] == ['Finish polish']
    assert delete_reminder.status_code == 204
    assert delete_goal.status_code == 204
