from types import SimpleNamespace

import pytest
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine

import app.models  # noqa: F401
from app.database import Base
from app.models.user import User
from app.services.auth_service import verify_password
from app.services.admin_bootstrap_service import ensure_admin_user


@pytest.mark.asyncio
async def test_ensure_admin_user_creates_missing_admin(tmp_path):
    db_path = tmp_path / 'test_admin_bootstrap.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)

    settings = SimpleNamespace(
        ADMIN='admin',
        ADMIN_EMAIL='admin@example.com',
        ADMIN_PASSWORD='secret123',
        ADMIN_FULL_NAME='Administrator',
    )

    async with session_factory() as session:
        await ensure_admin_user(session, settings)
        result = await session.execute(select(User).where(User.username == 'admin'))
        admin = result.scalar_one()

    assert admin.email == 'admin@example.com'
    assert admin.full_name == 'Administrator'
    assert admin.is_active is True
    assert admin.is_superuser is True
    assert verify_password('secret123', admin.hashed_password)

    await engine.dispose()


@pytest.mark.asyncio
async def test_ensure_admin_user_skips_when_target_admin_already_exists(tmp_path):
    db_path = tmp_path / 'test_admin_bootstrap_existing.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)

    settings = SimpleNamespace(
        ADMIN='admin',
        ADMIN_EMAIL='admin@example.com',
        ADMIN_PASSWORD='newsecret123',
        ADMIN_FULL_NAME='Administrator',
    )

    async with session_factory() as session:
        existing_admin = User(
            username='admin',
            email='admin@example.com',
            hashed_password='existing-hash',
            full_name='Existing Admin',
            is_active=True,
            is_superuser=True,
        )
        session.add(existing_admin)
        await session.commit()

        await ensure_admin_user(session, settings)
        result = await session.execute(select(User).where(User.username == 'admin'))
        admins = result.scalars().all()

    assert len(admins) == 1
    assert admins[0].hashed_password == 'existing-hash'
    assert admins[0].full_name == 'Existing Admin'

    await engine.dispose()


@pytest.mark.asyncio
async def test_ensure_admin_user_skips_when_bootstrap_not_enabled(tmp_path):
    db_path = tmp_path / 'test_admin_bootstrap_disabled.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)

    settings = SimpleNamespace(
        ADMIN='',
        ADMIN_EMAIL='admin@example.com',
        ADMIN_PASSWORD='',
        ADMIN_FULL_NAME='Administrator',
    )

    async with session_factory() as session:
        await ensure_admin_user(session, settings)
        result = await session.execute(select(User))
        users = result.scalars().all()

    assert users == []

    await engine.dispose()


@pytest.mark.asyncio
async def test_ensure_admin_user_raises_for_conflicting_non_admin_user(tmp_path):
    db_path = tmp_path / 'test_admin_bootstrap_conflict.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)

    settings = SimpleNamespace(
        ADMIN='admin',
        ADMIN_EMAIL='admin@example.com',
        ADMIN_PASSWORD='secret123',
        ADMIN_FULL_NAME='Administrator',
    )

    async with session_factory() as session:
        session.add(User(
            username='admin',
            email='someone@example.com',
            hashed_password='hash',
            full_name='Existing User',
            is_active=True,
            is_superuser=False,
        ))
        await session.commit()

        with pytest.raises(RuntimeError):
            await ensure_admin_user(session, settings)

    await engine.dispose()


@pytest.mark.asyncio
async def test_ensure_admin_user_succeeds_when_duplicate_insert_was_created_concurrently(tmp_path):
    db_path = tmp_path / 'test_admin_bootstrap_duplicate.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)

    settings = SimpleNamespace(
        ADMIN='admin',
        ADMIN_EMAIL='admin@example.com',
        ADMIN_PASSWORD='secret123',
        ADMIN_FULL_NAME='Administrator',
    )

    async with session_factory() as session:
        duplicate_admin = User(
            username='admin',
            email='admin@example.com',
            hashed_password='existing-hash',
            full_name='Existing Admin',
            is_active=True,
            is_superuser=True,
        )
        session.add(duplicate_admin)
        await session.flush()

        original_commit = session.commit

        async def fake_commit():
            await session.rollback()
            raise IntegrityError('insert', {}, Exception('duplicate'))

        session.commit = fake_commit
        try:
            await ensure_admin_user(session, settings)
        finally:
            session.commit = original_commit

    await engine.dispose()
