import pytest
from fastapi import FastAPI
from httpx import ASGITransport, AsyncClient
from sqlalchemy import select, text
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.skill import Skill
from app.models.user import User
from app.routers.auth import get_current_user
from app.routers.skill import router as skill_router
from app.routers.auth import router as auth_router
from app.services.auth_service import get_password_hash


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

    # Ensure app.database.init_db() runs against the test database.
    import app.database as database_module

    monkeypatch.setattr(database_module, "engine", engine)
    monkeypatch.setattr(database_module, "async_session", session_factory)

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

    async with session_factory() as session:
        user = User(
            username='skill_user',
            email='skill@example.com',
            hashed_password=get_password_hash('secret123'),
            full_name='Skill Tester',
        )
        other_user = User(
            username='other_skill_user',
            email='other-skill@example.com',
            hashed_password=get_password_hash('secret123'),
            full_name='Other Skill Tester',
        )
        session.add_all([user, other_user])
        await session.flush()
        session.add_all([
            Skill(
                name='Planner skill',
                description='planner',
                instructions='plan',
                agent_type='schedule_planner',
                tools=['calendar'],
                required_context=[],
                visibility='private',
                is_active=True,
                status='active',
                scope=['schedule_planner'],
                effectiveness=0.88,
                owner_id=user.id,
            ),
            Skill(
                name='Executor skill',
                description='executor',
                instructions='execute',
                agent_type='executor',
                tools=['shell'],
                required_context=[],
                visibility='private',
                is_active=True,
                status='shadow',
                scope=['executor'],
                effectiveness=0.41,
                owner_id=user.id,
            ),
            Skill(
                name='Other user planner skill',
                description='other',
                instructions='other',
                agent_type='schedule_planner',
                tools=['calendar'],
                required_context=[],
                visibility='private',
                is_active=True,
                status='active',
                scope=['schedule_planner'],
                owner_id=other_user.id,
            ),
        ])
        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(auth_router)
    test_app.include_router(skill_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, session_factory
    finally:
        await engine.dispose()


@pytest.mark.asyncio
async def test_list_skills_filters_by_agent_type(skill_env):
    test_app, _session_factory = skill_env
    transport = ASGITransport(app=test_app)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.get('/api/skills', params={'agent_type': 'schedule_planner'})

    assert response.status_code == 200
    payload = response.json()
    names = {item['name'] for item in payload}
    assert names == {'Planner skill'}
    assert 'Other user planner skill' not in names


@pytest.mark.asyncio
async def test_init_db_migrates_planner_skills_to_schedule_planner(skill_env):
    app, session_factory = skill_env
    async with session_factory() as session:
        await session.execute(text("UPDATE skills SET agent_type = 'planner' WHERE name = 'Planner skill'"))
        await session.commit()

    from app.database import init_db

    await init_db()

    async with session_factory() as session:
        migrated_response = await session.execute(text("SELECT agent_type FROM skills WHERE name = 'Planner skill'"))
        assert migrated_response.scalar_one() == 'schedule_planner'


@pytest.mark.asyncio
async def test_list_skills_visibility_filter_still_respects_access_scope(skill_env):
    test_app, _session_factory = skill_env
    transport = ASGITransport(app=test_app)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.get('/api/skills', params={'visibility': 'private'})

    assert response.status_code == 200
    payload = response.json()
    names = {item['name'] for item in payload}
    assert names == {'Planner skill', 'Executor skill'}
    assert 'Other user planner skill' not in names


@pytest.mark.asyncio
async def test_list_skills_bootstraps_builtin_market_skills_for_current_user(skill_env):
    test_app, session_factory = skill_env
    async with session_factory() as session:
        await session.execute(text("DELETE FROM skills"))
        await session.commit()

    transport = ASGITransport(app=test_app)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        login_response = await client.post(
            '/api/auth/login',
            data={'username': 'skill_user', 'password': 'secret123'},
            headers={'content-type': 'application/x-www-form-urlencoded'},
        )

    assert login_response.status_code == 200

    async with session_factory() as session:
        result = await session.execute(select(Skill.name, Skill.is_builtin).order_by(Skill.name))
        skills = result.all()

    names = {name for name, _is_builtin in skills}
    assert '今日重点拆解' in names
    assert '任务执行 SOP' in names
    assert any(is_builtin is True for _name, is_builtin in skills)


@pytest.mark.asyncio
async def test_list_skills_without_agent_type_returns_current_user_skills(skill_env):
    test_app, _session_factory = skill_env
    transport = ASGITransport(app=test_app)
    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.get('/api/skills')

    assert response.status_code == 200
    payload = response.json()
    names = {item['name'] for item in payload}
    assert names == {'Planner skill', 'Executor skill'}
    assert 'Other user planner skill' not in names
    assert all(isinstance(item['created_at'], str) for item in payload)
    assert all(isinstance(item['updated_at'], str) for item in payload)
    assert all('is_builtin' in item for item in payload)
    assert all(item['is_builtin'] is False for item in payload)
    assert all('status' in item for item in payload)
    assert all('scope' in item for item in payload)
    assert any(item['status'] == 'shadow' for item in payload)
    executor = next(item for item in payload if item['name'] == 'Executor skill')
    assert executor['scope'] == ['executor']
    assert executor['effectiveness'] == 0.41
