# app/crud/syndarix/agent_type.py
"""Async CRUD operations for AgentType model using SQLAlchemy 2.0 patterns."""

import logging
from typing import Any
from uuid import UUID

from sqlalchemy import func, or_, select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession

from app.crud.base import CRUDBase
from app.models.syndarix import AgentInstance, AgentType
from app.schemas.syndarix import AgentTypeCreate, AgentTypeUpdate

logger = logging.getLogger(__name__)


class CRUDAgentType(CRUDBase[AgentType, AgentTypeCreate, AgentTypeUpdate]):
    """Async CRUD operations for AgentType model."""

    async def get_by_slug(self, db: AsyncSession, *, slug: str) -> AgentType | None:
        """Get agent type by slug."""
        try:
            result = await db.execute(
                select(AgentType).where(AgentType.slug == slug)
            )
            return result.scalar_one_or_none()
        except Exception as e:
            logger.error(f"Error getting agent type by slug {slug}: {e!s}")
            raise

    async def create(
        self, db: AsyncSession, *, obj_in: AgentTypeCreate
    ) -> AgentType:
        """Create a new agent type with error handling."""
        try:
            db_obj = AgentType(
                name=obj_in.name,
                slug=obj_in.slug,
                description=obj_in.description,
                expertise=obj_in.expertise,
                personality_prompt=obj_in.personality_prompt,
                primary_model=obj_in.primary_model,
                fallback_models=obj_in.fallback_models,
                model_params=obj_in.model_params,
                mcp_servers=obj_in.mcp_servers,
                tool_permissions=obj_in.tool_permissions,
                is_active=obj_in.is_active,
            )
            db.add(db_obj)
            await db.commit()
            await db.refresh(db_obj)
            return db_obj
        except IntegrityError as e:
            await db.rollback()
            error_msg = str(e.orig) if hasattr(e, "orig") else str(e)
            if "slug" in error_msg.lower():
                logger.warning(f"Duplicate slug attempted: {obj_in.slug}")
                raise ValueError(
                    f"Agent type with slug '{obj_in.slug}' already exists"
                )
            logger.error(f"Integrity error creating agent type: {error_msg}")
            raise ValueError(f"Database integrity error: {error_msg}")
        except Exception as e:
            await db.rollback()
            logger.error(
                f"Unexpected error creating agent type: {e!s}", exc_info=True
            )
            raise

    async def get_multi_with_filters(
        self,
        db: AsyncSession,
        *,
        skip: int = 0,
        limit: int = 100,
        is_active: bool | None = None,
        search: str | None = None,
        sort_by: str = "created_at",
        sort_order: str = "desc",
    ) -> tuple[list[AgentType], int]:
        """
        Get multiple agent types with filtering, searching, and sorting.

        Returns:
            Tuple of (agent types list, total count)
        """
        try:
            query = select(AgentType)

            # Apply filters
            if is_active is not None:
                query = query.where(AgentType.is_active == is_active)

            if search:
                search_filter = or_(
                    AgentType.name.ilike(f"%{search}%"),
                    AgentType.slug.ilike(f"%{search}%"),
                    AgentType.description.ilike(f"%{search}%"),
                )
                query = query.where(search_filter)

            # Get total count before pagination
            count_query = select(func.count()).select_from(query.alias())
            count_result = await db.execute(count_query)
            total = count_result.scalar_one()

            # Apply sorting
            sort_column = getattr(AgentType, sort_by, AgentType.created_at)
            if sort_order == "desc":
                query = query.order_by(sort_column.desc())
            else:
                query = query.order_by(sort_column.asc())

            # Apply pagination
            query = query.offset(skip).limit(limit)
            result = await db.execute(query)
            agent_types = list(result.scalars().all())

            return agent_types, total
        except Exception as e:
            logger.error(f"Error getting agent types with filters: {e!s}")
            raise

    async def get_with_instance_count(
        self,
        db: AsyncSession,
        *,
        agent_type_id: UUID,
    ) -> dict[str, Any] | None:
        """
        Get a single agent type with its instance count.

        Returns:
            Dictionary with agent_type and instance_count
        """
        try:
            result = await db.execute(
                select(AgentType).where(AgentType.id == agent_type_id)
            )
            agent_type = result.scalar_one_or_none()

            if not agent_type:
                return None

            # Get instance count
            count_result = await db.execute(
                select(func.count(AgentInstance.id)).where(
                    AgentInstance.agent_type_id == agent_type_id
                )
            )
            instance_count = count_result.scalar_one()

            return {
                "agent_type": agent_type,
                "instance_count": instance_count,
            }
        except Exception as e:
            logger.error(
                f"Error getting agent type with count {agent_type_id}: {e!s}",
                exc_info=True,
            )
            raise

    async def get_multi_with_instance_counts(
        self,
        db: AsyncSession,
        *,
        skip: int = 0,
        limit: int = 100,
        is_active: bool | None = None,
        search: str | None = None,
    ) -> tuple[list[dict[str, Any]], int]:
        """
        Get agent types with instance counts in optimized queries.

        Returns:
            Tuple of (list of dicts with agent_type and instance_count, total count)
        """
        try:
            # Get filtered agent types
            agent_types, total = await self.get_multi_with_filters(
                db,
                skip=skip,
                limit=limit,
                is_active=is_active,
                search=search,
            )

            if not agent_types:
                return [], 0

            agent_type_ids = [at.id for at in agent_types]

            # Get instance counts in bulk
            counts_result = await db.execute(
                select(
                    AgentInstance.agent_type_id,
                    func.count(AgentInstance.id).label("count"),
                )
                .where(AgentInstance.agent_type_id.in_(agent_type_ids))
                .group_by(AgentInstance.agent_type_id)
            )
            counts = {row.agent_type_id: row.count for row in counts_result}

            # Combine results
            results = [
                {
                    "agent_type": agent_type,
                    "instance_count": counts.get(agent_type.id, 0),
                }
                for agent_type in agent_types
            ]

            return results, total
        except Exception as e:
            logger.error(
                f"Error getting agent types with counts: {e!s}", exc_info=True
            )
            raise

    async def get_by_expertise(
        self,
        db: AsyncSession,
        *,
        expertise: str,
        is_active: bool = True,
    ) -> list[AgentType]:
        """Get agent types that have a specific expertise."""
        try:
            # Use PostgreSQL JSONB contains operator
            query = select(AgentType).where(
                AgentType.expertise.contains([expertise.lower()]),
                AgentType.is_active == is_active,
            )
            result = await db.execute(query)
            return list(result.scalars().all())
        except Exception as e:
            logger.error(
                f"Error getting agent types by expertise {expertise}: {e!s}",
                exc_info=True,
            )
            raise

    async def deactivate(
        self,
        db: AsyncSession,
        *,
        agent_type_id: UUID,
    ) -> AgentType | None:
        """Deactivate an agent type (soft delete)."""
        try:
            result = await db.execute(
                select(AgentType).where(AgentType.id == agent_type_id)
            )
            agent_type = result.scalar_one_or_none()

            if not agent_type:
                return None

            agent_type.is_active = False
            await db.commit()
            await db.refresh(agent_type)
            return agent_type
        except Exception as e:
            await db.rollback()
            logger.error(
                f"Error deactivating agent type {agent_type_id}: {e!s}", exc_info=True
            )
            raise


# Create a singleton instance for use across the application
agent_type = CRUDAgentType(AgentType)
