"""Shared test fixtures for all test categories."""

import asyncio
import os
from typing import AsyncGenerator, Generator
from unittest.mock import patch

import pytest
import pytest_asyncio
from fastapi.testclient import TestClient
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine

# Set test environment BEFORE importing app modules
os.environ["APP_ENV"] = "testing"
os.environ["SECRET_KEY"] = "test-secret-key-for-testing-only-do-not-use-in-production"
os.environ["DATABASE_URL"] = "sqlite+aiosqlite:///:memory:"

from src.config import Settings
from src.models.base import Base
from src.main import app
from src.auth.dependencies import get_db_session


@pytest.fixture
def test_client() -> Generator[TestClient, None, None]:
    """Provide a FastAPI test client with SQLite database."""
    # Create a single engine for this test
    engine = create_async_engine(
        "sqlite+aiosqlite:///:memory:",
        connect_args={"check_same_thread": False},
    )
    
    # Create tables
    async def init_db():
        async with engine.begin() as conn:
            await conn.run_sync(Base.metadata.create_all)
    
    asyncio.run(init_db())
    
    async def override_get_db_session() -> AsyncGenerator[AsyncSession, None]:
        async with async_sessionmaker(engine, expire_on_commit=False)() as session:
            yield session

    # Override the dependency
    app.dependency_overrides[get_db_session] = override_get_db_session
    
    # Patch startup events to prevent PostgreSQL connection attempts
    with patch("src.main.init_database") as mock_init:
        mock_init.return_value = True
        
        try:
            with TestClient(app) as client:
                yield client
        finally:
            # Clean up overrides
            app.dependency_overrides.pop(get_db_session, None)
            asyncio.run(engine.dispose())


@pytest_asyncio.fixture
async def db_session(test_client) -> AsyncGenerator[AsyncSession, None]:
    """Provide an async database session for unit tests."""
    # Get the override function from the test_client fixture
    override_fn = app.dependency_overrides.get(get_db_session)
    if override_fn:
        gen = override_fn()
        session = await gen.asend(None)
        try:
            yield session
        finally:
            await gen.aclose()
    else:
        # Fallback: create a new engine and session
        engine = create_async_engine(
            "sqlite+aiosqlite:///:memory:",
            connect_args={"check_same_thread": False},
        )
        async with engine.begin() as conn:
            await conn.run_sync(Base.metadata.create_all)
        async with async_sessionmaker(engine, expire_on_commit=False)() as session:
            yield session
        await engine.dispose()


@pytest.fixture
def authenticated_client(test_client) -> Generator[TestClient, None, None]:
    """Provide an authenticated test client with a test user."""
    import uuid
    from src.auth.session import create_session_cookie
    from src.models.user.user import User
    
    user_id = str(uuid.uuid4())
    settings = Settings()
    
    # Create user in database using the same engine as test_client
    # We need to access the engine from the test_client fixture
    # Since we can't easily do that, we'll create the user via API call
    # But we need the user to exist before any API calls
    # So we need to create the user using the overridden dependency
    
    async def create_test_user():
        # Get the override function
        override_fn = app.dependency_overrides.get(get_db_session)
        if override_fn:
            gen = override_fn()
            session = await gen.asend(None)
            try:
                user = User(
                    id=uuid.UUID(user_id),
                    email="test@headquarter.local",
                    name="Test User",
                    authentik_id=f"authentik-{user_id}",
                    avatar_url=None,
                )
                session.add(user)
                await session.commit()
            finally:
                await gen.aclose()
    
    asyncio.run(create_test_user())
    
    # Create session cookie
    session_cookie = create_session_cookie(
        settings=settings,
        user_id=user_id,
    )
    
    # Set cookie on client
    test_client.cookies.set("session", session_cookie)
    
    yield test_client


@pytest.fixture
def test_project_and_repo(authenticated_client) -> tuple[str, str]:
    """Create a project and repository directly in the database."""
    import uuid
    from src.models.project.project import Project
    from src.models.project.git_repository import GitRepository
    
    project_id = uuid.uuid4()
    repo_id = uuid.uuid4()
    user_id = None
    
    # Get user ID from session
    async def get_user_id():
        nonlocal user_id
        from src.auth.session import decode_session_cookie
        settings = Settings()
        session_cookie = authenticated_client.cookies.get("session")
        if session_cookie:
            session = decode_session_cookie(settings=settings, cookie_value=session_cookie)
            if session:
                user_id = uuid.UUID(session["user_id"])
    
    asyncio.run(get_user_id())
    
    if not user_id:
        raise RuntimeError("Could not get user ID from authenticated client")
    
    async def create_project_and_repo():
        override_fn = app.dependency_overrides.get(get_db_session)
        if override_fn:
            gen = override_fn()
            session = await gen.asend(None)
            try:
                project = Project(
                    id=project_id,
                    name="test-project",
                    description="Test project",
                    owner_id=user_id,
                )
                session.add(project)
                
                repo = GitRepository(
                    id=repo_id,
                    name="test-repo",
                    path="/tmp/test-repo",
                    project_id=project_id,
                    owner_id=user_id,
                    remote_url="https://github.com/test/repo.git",
                )
                session.add(repo)
                await session.commit()
            finally:
                await gen.aclose()
    
    asyncio.run(create_project_and_repo())
    
    return str(project_id), str(repo_id)


@pytest.fixture
def admin_client(test_client) -> Generator[TestClient, None, None]:
    """Provide an authenticated test client with an admin user."""
    import uuid
    from src.auth.session import create_session_cookie
    from src.models.user.user import User
    
    user_id = str(uuid.uuid4())
    settings = Settings()
    
    async def create_admin_user():
        override_fn = app.dependency_overrides.get(get_db_session)
        if override_fn:
            gen = override_fn()
            session = await gen.asend(None)
            try:
                user = User(
                    id=uuid.UUID(user_id),
                    email="admin@headquarter.local",
                    name="Admin User",
                    authentik_id=f"authentik-admin-{user_id}",
                    avatar_url=None,
                    is_admin=True,
                )
                session.add(user)
                await session.commit()
            finally:
                await gen.aclose()
    
    asyncio.run(create_admin_user())
    
    # Create session cookie
    session_cookie = create_session_cookie(
        settings=settings,
        user_id=user_id,
    )
    
    # Set cookie on client
    test_client.cookies.set("session", session_cookie)
    
    yield test_client
