import pytest
from httpx import ASGITransport, AsyncClient
from jose import jwt
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine

import app.models  # noqa: F401
from app.config import settings
from app.database import Base, get_db
from app.main import app
from app.models.user import User
from app.services.auth_service import get_password_hash


@pytest.fixture
async def auth_test_env(tmp_path):
    db_path = tmp_path / 'test_auth.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:
        username_user = User(
            username='jarvis_admin',
            email='admin@example.com',
            hashed_password=get_password_hash('secret123'),
            full_name='Admin User',
            is_active=True,
        )
        prefix_user = User(
            username='other_user',
            email='jarvis_admin@example.com',
            hashed_password=get_password_hash('othersecret123'),
            full_name='Prefix User',
            is_active=True,
        )
        inactive_user = User(
            username='disabled_user',
            email='disabled@example.com',
            hashed_password=get_password_hash('secret123'),
            full_name='Disabled User',
            is_active=False,
        )
        ambiguous_user_one = User(
            username='alpha_user',
            email='alice@example.com',
            hashed_password=get_password_hash('secret123'),
            full_name='Alice One',
            is_active=True,
        )
        ambiguous_user_two = User(
            username='beta_user',
            email='alice@another.com',
            hashed_password=get_password_hash('secret123'),
            full_name='Alice Two',
            is_active=True,
        )
        fallback_user = User(
            username='fallback_target',
            email='fallback@example.com',
            hashed_password=get_password_hash('fallbacksecret123'),
            full_name='Fallback User',
            is_active=True,
        )
        wildcard_user = User(
            username='wildcard_target',
            email='alice1@example.com',
            hashed_password=get_password_hash('wildsecret123'),
            full_name='Wildcard User',
            is_active=True,
        )
        session.add_all([
            username_user,
            prefix_user,
            inactive_user,
            ambiguous_user_one,
            ambiguous_user_two,
            fallback_user,
            wildcard_user,
        ])
        await session.commit()
        await session.refresh(username_user)
        await session.refresh(inactive_user)
        await session.refresh(fallback_user)
        await session.refresh(wildcard_user)

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

    app.dependency_overrides[get_db] = override_get_db

    try:
        yield {
            'username_user': username_user,
            'inactive_user': inactive_user,
            'prefix_user': prefix_user,
            'fallback_user': fallback_user,
            'wildcard_user': wildcard_user,
        }
    finally:
        app.dependency_overrides.clear()
        await engine.dispose()


@pytest.mark.asyncio
async def test_login_accepts_username_and_returns_token(auth_test_env):
    transport = ASGITransport(app=app)

    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.post(
            '/api/auth/login',
            data={'username': 'jarvis_admin', 'password': 'secret123'},
        )

    assert response.status_code == 200
    payload = response.json()
    assert payload['token_type'] == 'bearer'
    assert payload['access_token']
    claims = jwt.decode(payload['access_token'], settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
    assert claims['sub'] == auth_test_env['username_user'].id


@pytest.mark.asyncio
async def test_login_prefers_username_over_email_prefix(auth_test_env):
    transport = ASGITransport(app=app)

    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.post(
            '/api/auth/login',
            data={'username': 'jarvis_admin', 'password': 'secret123'},
        )

    assert response.status_code == 200
    claims = jwt.decode(response.json()['access_token'], settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
    assert claims['sub'] == auth_test_env['username_user'].id


@pytest.mark.asyncio
async def test_login_rejects_wrong_password_for_username(auth_test_env):
    transport = ASGITransport(app=app)

    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.post(
            '/api/auth/login',
            data={'username': 'jarvis_admin', 'password': 'wrong-password'},
        )

    assert response.status_code == 401
    assert response.json()['detail'] == '用户名、邮箱或密码错误'


@pytest.mark.asyncio
async def test_login_rejects_inactive_username_user(auth_test_env):
    transport = ASGITransport(app=app)

    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.post(
            '/api/auth/login',
            data={'username': 'disabled_user', 'password': 'secret123'},
        )

    assert response.status_code == 403
    assert response.json()['detail'] == '用户已被禁用'


@pytest.mark.asyncio
async def test_register_requires_username_and_returns_it(auth_test_env):
    transport = ASGITransport(app=app)

    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.post(
            '/api/auth/register',
            json={
                'username': 'new_user',
                'email': 'new@example.com',
                'password': 'secret123',
                'full_name': 'New User',
            },
        )

    assert response.status_code == 201
    payload = response.json()
    assert payload['username'] == 'new_user'
    assert payload['email'] == 'new@example.com'


@pytest.mark.asyncio
async def test_login_accepts_exact_email(auth_test_env):
    transport = ASGITransport(app=app)

    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.post(
            '/api/auth/login',
            data={'username': 'admin@example.com', 'password': 'secret123'},
        )

    assert response.status_code == 200
    claims = jwt.decode(response.json()['access_token'], settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
    assert claims['sub'] == auth_test_env['username_user'].id


@pytest.mark.asyncio
async def test_login_accepts_uuid_before_other_identifier_strategies(auth_test_env):
    transport = ASGITransport(app=app)

    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.post(
            '/api/auth/login',
            data={'username': auth_test_env['username_user'].id, 'password': 'secret123'},
        )

    assert response.status_code == 200
    claims = jwt.decode(response.json()['access_token'], settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
    assert claims['sub'] == auth_test_env['username_user'].id


@pytest.mark.asyncio
async def test_login_accepts_unique_email_prefix(auth_test_env):
    transport = ASGITransport(app=app)

    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.post(
            '/api/auth/login',
            data={'username': 'fallback', 'password': 'fallbacksecret123'},
        )

    assert response.status_code == 200
    claims = jwt.decode(response.json()['access_token'], settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
    assert claims['sub'] == auth_test_env['fallback_user'].id


@pytest.mark.asyncio
async def test_login_rejects_ambiguous_email_prefix(auth_test_env):
    transport = ASGITransport(app=app)

    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.post(
            '/api/auth/login',
            data={'username': 'alice', 'password': 'secret123'},
        )

    assert response.status_code == 401
    assert response.json()['detail'] == '用户名、邮箱或密码错误'


@pytest.mark.asyncio
async def test_login_does_not_treat_like_wildcards_as_email_prefix_patterns(auth_test_env):
    transport = ASGITransport(app=app)

    async with AsyncClient(transport=transport, base_url='http://testserver') as client:
        response = await client.post(
            '/api/auth/login',
            data={'username': 'alice_', 'password': 'wildsecret123'},
        )

    assert response.status_code == 401
    assert response.json()['detail'] == '用户名、邮箱或密码错误'
