"""
SwX Testing Utilities.

Provides utilities for testing FastAPI applications.
"""
import asyncio
import uuid
import random
import string
from contextlib import asynccontextmanager
from typing import List, Dict, Any, AsyncGenerator, Type, TypeVar, Optional

from fastapi.testclient import TestClient
from httpx import AsyncClient, ASGITransport
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
from sqlmodel import SQLModel

from swx_core.database.db import get_session


# Type variable for models
ModelType = TypeVar("ModelType", bound=SQLModel)


class TestSession:
    """Test session wrapper for sync tests."""
    
    def __init__(self, session: AsyncSession):
        self._session = session
    
    def add(self, instance):
        """Add instance to session."""
        self._session.add(instance)
    
    def commit(self):
        """Commit session - sync wrapper."""
        loop = asyncio.get_event_loop()
        loop.run_until_complete(self._session.commit())
    
    def refresh(self, instance):
        """Refresh instance - sync wrapper."""
        loop = asyncio.get_event_loop()
        loop.run_until_complete(self._session.refresh(instance))
    
    def delete(self, instance):
        """Delete instance - sync wrapper."""
        self._session.delete(instance)
    
    def execute(self, statement):
        """Execute statement - sync wrapper."""
        loop = asyncio.get_event_loop()
        return loop.run_until_complete(self._session.execute(statement))
    
    def scalar(self, statement):
        """Execute and return scalar - sync wrapper."""
        loop = asyncio.get_event_loop()
        return loop.run_until_complete(self._session.scalar(statement))


class TestDatabase:
    """Test database manager for setting up test databases."""
    
    def __init__(self, database_url: str = "sqlite+aiosqlite:///:memory:"):
        self.database_url = database_url
        self.engine = None
        self.async_session_maker = None
    
    async def setup(self):
        """Create database tables."""
        self.engine = create_async_engine(self.database_url, echo=False)
        self.async_session_maker = async_sessionmaker(
            self.engine,
            class_=AsyncSession,
            expire_on_commit=False
        )
        
        async with self.engine.begin() as conn:
            await conn.run_sync(SQLModel.metadata.create_all)
    
    async def teardown(self):
        """Drop all tables and dispose engine."""
        async with self.engine.begin() as conn:
            await conn.run_sync(SQLModel.metadata.drop_all)
        
        await self.engine.dispose()
    
    async def get_session(self) -> AsyncGenerator[AsyncSession, None]:
        """Get async session."""
        async with self.async_session_maker() as session:
            yield session


class TestClientWithDB:
    """Test client with database support."""
    
    def __init__(self, app, db_url: str = "sqlite+aiosqlite:///:memory:"):
        self.app = app
        self.db = TestDatabase(db_url)
        self._client = None
    
    async def setup(self):
        """Setup test client and database."""
        await self.db.setup()
        self._client = TestClient(self.app)
    
    async def teardown(self):
        """Teardown test client and database."""
        await self.db.teardown()
    
    def get(self, *args, **kwargs):
        """Make GET request."""
        return self._client.get(*args, **kwargs)
    
    def post(self, *args, **kwargs):
        """Make POST request."""
        return self._client.post(*args, **kwargs)
    
    def put(self, *args, **kwargs):
        """Make PUT request."""
        return self._client.put(*args, **kwargs)
    
    def delete(self, *args, **kwargs):
        """Make DELETE request."""
        return self._client.delete(*args, **kwargs)
    
    def patch(self, *args, **kwargs):
        """Make PATCH request."""
        return self._client.patch(*args, **kwargs)


class ModelFactory:
    """
    Factory for creating test model instances.
    
    Usage:
        user_factory = ModelFactory(User)
        user = user_factory.create(email="test@example.com", name="Test User")
    """
    
    def __init__(self, model_class: Type[ModelType]):
        self.model_class = model_class
        self._defaults = {}
    
    def create(self, **kwargs) -> ModelType:
        """Create model instance with defaults."""
        data = {**self._defaults, **kwargs}
        return self.model_class(**data)
    
    def create_batch(self, count: int, **kwargs) -> List[ModelType]:
        """Create multiple model instances."""
        return [self.create(**kwargs) for _ in range(count)]
    
    def defaults(self, **kwargs) -> "ModelFactory":
        """Set default values for factory."""
        self._defaults.update(kwargs)
        return self


# Async test mixin
class AsyncTestMixin:
    """Mixin for async tests."""
    
    @staticmethod
    async def run_async(coro):
        """Run async function in test."""
        return await coro


# Random data generators
def random_uuid() -> uuid.UUID:
    """Generate random UUID."""
    return uuid.uuid4()


def random_email() -> str:
    """Generate random email address."""
    letters = string.ascii_lowercase
    username = ''.join(random.choices(letters, k=10))
    return f"{username}@example.com"


def random_string(length: int = 10) -> str:
    """Generate random string."""
    return ''.join(random.choices(string.ascii_letters + string.digits, k=length))


# Assertion helpers
def assert_response_status(response, expected_status: int = 200):
    """Assert response has expected status code."""
    assert response.status_code == expected_status, \
        f"Expected status {expected_status}, got {response.status_code}. Response: {response.text}"


def assert_response_json(response, expected_keys: List[str] = None):
    """Assert response is valid JSON with expected keys."""
    data = response.json()
    if expected_keys:
        for key in expected_keys:
            assert key in data, f"Key '{key}' not found in response: {data}"
    return data


def assert_model_equal(model1: SQLModel, model2: SQLModel, exclude: List[str] = None):
    """Assert two models have equal field values."""
    exclude = exclude or ["id", "created_at", "updated_at"]
    
    for field in model1.model_fields:
        if field not in exclude:
            value1 = getattr(model1, field, None)
            value2 = getattr(model2, field, None)
            assert value1 == value2, \
                f"Field '{field}' mismatch: {value1} != {value2}"


# Context managers
@asynccontextmanager
async def async_test_context():
    """Context manager for async tests."""
    loop = asyncio.get_event_loop()
    yield loop


# Fixtures utility
async def create_fixture_data(models: List[Dict[str, Any]], session: AsyncSession):
    """
    Create fixture data in database.
    
    Usage:
        @pytest.fixture
        async def users(session):
            data = [
                {"model": User, "data": {"email": "user1@example.com", "name": "User One"}},
                {"model": User, "data": {"email": "user2@example.com", "name": "User Two"}},
            ]
            return await create_fixture_data(data, session)
    """
    instances = []
    for model_data in models:
        instance = model_data.get("model")(**model_data.get("data", {}))
        session.add(instance)
        instances.append(instance)
    
    await session.commit()
    
    for instance in instances:
        await session.refresh(instance)
    
    return instances


__all__ = [
    "TestSession",
    "TestDatabase",
    "TestClientWithDB",
    "ModelFactory",
    "AsyncTestMixin",
    "random_uuid",
    "random_email",
    "random_string",
    "assert_response_status",
    "assert_response_json",
    "assert_model_equal",
    "create_fixture_data",
    "async_test_context",
]