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

import app.models  # noqa: F401
from app.database import Base, get_db
from app.models.agent import Agent
from app.models.skill import Skill
from app.models.user import User
from app.routers.agent import router as agent_router
from app.routers.auth import get_current_user
from app.services.auth_service import get_password_hash


@pytest.fixture
async def agent_env(tmp_path):
    db_path = tmp_path / 'test_agent_router.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='agent_user',
            email='agent@example.com',
            hashed_password=get_password_hash('secret123'),
            full_name='Agent Tester',
        )
        session.add(user)
        await session.flush()
        skill_a = Skill(
            name='Planner skill A',
            description='planner',
            instructions='plan a',
            agent_type='schedule_planner',
            tools=['calendar'],
            required_context=[],
            visibility='private',
            is_active=True,
            owner_id=user.id,
        )
        skill_b = Skill(
            name='Planner skill B',
            description='planner',
            instructions='plan b',
            agent_type='schedule_planner',
            tools=['tasks'],
            required_context=[],
            visibility='private',
            is_active=True,
            owner_id=user.id,
        )
        agent = Agent(
            name='SCHEDULE PLANNER',
            role='schedule_planner',
            description='日程规划师',
            system_prompt='prompt',
            is_active=True,
        )
        session.add_all([agent, skill_a, skill_b])
        await session.commit()
        await session.refresh(user)
        await session.refresh(agent)
        await session.refresh(skill_a)
        await session.refresh(skill_b)

    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(agent_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, {'agent_id': agent.id, 'skill_a_id': skill_a.id, 'skill_b_id': skill_b.id}
    finally:
        await engine.dispose()


@pytest.mark.asyncio
async def test_get_agent_config_returns_default_empty_selected_skill_ids(agent_env):
    app, _ids = agent_env
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.get('/api/agents/config/schedule_planner')

    assert response.status_code == 200
    payload = response.json()
    assert payload['selected_skill_ids'] == []


@pytest.mark.asyncio
async def test_update_agent_config_persists_selected_skill_ids(agent_env):
    app, ids = agent_env
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        update_response = await client.put(
            '/api/agents/config/schedule_planner',
            json={'selected_skill_ids': [ids['skill_a_id'], ids['skill_b_id']]},
        )
        get_response = await client.get('/api/agents/config/schedule_planner')

    assert update_response.status_code == 200
    assert update_response.json()['selected_skill_ids'] == [ids['skill_a_id'], ids['skill_b_id']]
    assert get_response.status_code == 200
    assert get_response.json()['selected_skill_ids'] == [ids['skill_a_id'], ids['skill_b_id']]


@pytest.mark.asyncio
async def test_get_agent_config_requires_authentication(agent_env):
    app, _ids = agent_env

    async def override_get_current_user_unauthorized():
        raise RuntimeError('should not be called')

    app.dependency_overrides.pop(get_current_user, None)
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.get('/api/agents/config/schedule_planner')

    assert response.status_code == 401


@pytest.mark.asyncio
async def test_get_agent_requires_authentication(agent_env):
    app, ids = agent_env
    app.dependency_overrides.pop(get_current_user, None)
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.get(f"/api/agents/{ids['agent_id']}")

    assert response.status_code == 401


@pytest.mark.asyncio
async def test_update_agent_config_preserves_selected_skill_ids_when_omitted(agent_env):
    app, ids = agent_env
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        first_response = await client.put(
            '/api/agents/config/schedule_planner',
            json={'selected_skill_ids': [ids['skill_a_id']]},
        )
        update_response = await client.put(
            '/api/agents/config/schedule_planner',
            json={'description': 'updated description'},
        )

    assert first_response.status_code == 200
    assert update_response.status_code == 200
    assert update_response.json()['description'] == 'updated description'
    assert update_response.json()['selected_skill_ids'] == [ids['skill_a_id']]


@pytest.mark.asyncio
async def test_update_agent_config_rejects_invalid_selected_skill_ids(agent_env):
    app, _ids = agent_env
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.put(
            '/api/agents/config/schedule_planner',
            json={'selected_skill_ids': ['missing-skill']},
        )

    assert response.status_code == 400
    assert response.json()['detail'] == '存在无效的技能绑定'


@pytest.mark.asyncio
async def test_create_agent_requires_superuser(agent_env):
    app, _ids = agent_env
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.post(
            '/api/agents',
            json={
                'name': 'Runtime Planner',
                'role': 'schedule_planning',
                'description': 'runtime',
                'system_prompt': 'prompt',
                'spawn_permission': True,
            },
        )

    assert response.status_code == 403
    assert response.json()['detail'] == '仅管理员可创建 Agent'


@pytest.mark.asyncio
async def test_create_agent_requires_spawn_permission_for_runtime_role(agent_env):
    app, _ids = agent_env

    async def override_admin_user():
        return User(
            username='admin_user',
            email='admin@example.com',
            hashed_password='x',
            is_superuser=True,
        )

    app.dependency_overrides[get_current_user] = override_admin_user
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.post(
            '/api/agents',
            json={
                'name': 'Runtime Planner',
                'role': 'schedule_planning',
                'description': 'runtime',
                'system_prompt': 'prompt',
            },
        )

    assert response.status_code == 400
    assert response.json()['detail'] == '缺少 spawn_permission，禁止直接创建 runtime agent'


@pytest.mark.asyncio
async def test_create_agent_accepts_allowed_runtime_role_for_superuser(agent_env):
    app, _ids = agent_env

    async def override_admin_user():
        return User(
            username='admin_user',
            email='admin@example.com',
            hashed_password='x',
            is_superuser=True,
        )

    app.dependency_overrides[get_current_user] = override_admin_user
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.post(
            '/api/agents',
            json={
                'name': 'Runtime Planner',
                'role': 'schedule_planning',
                'description': 'runtime',
                'system_prompt': 'prompt',
                'spawn_permission': True,
            },
        )

    assert response.status_code == 201
    payload = response.json()
    assert payload['name'] == 'Runtime Planner'
    assert payload['role'] == 'schedule_planning'
