"""Base service class for CRUD operations.

⚠️ AUTO-GENERATED BY PRISM - DO NOT EDIT
"""

from __future__ import annotations

from abc import ABC
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any, Protocol, Sequence, runtime_checkable

from sqlalchemy import select, func, update, delete
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from pydantic import BaseModel

if TYPE_CHECKING:
    from sqlalchemy.orm import DeclarativeBase


@runtime_checkable
class ModelProtocol(Protocol):
    """Protocol for SQLAlchemy models used in services."""

    id: Any
    __table__: Any


class ServiceBase[ModelT: ModelProtocol, CreateSchemaT: BaseModel, UpdateSchemaT: BaseModel](ABC):
    """Abstract base class for all services.

    Provides generic CRUD operations that can be extended
    with custom business logic.
    """

    model: type[ModelT]

    def __init__(self, db: AsyncSession) -> None:
        """Initialize the service with a database session.

        Args:
            db: The async database session.
        """
        self.db = db

    async def get(
        self,
        id: int,
        *,
        include_deleted: bool = False,
        load_relationships: list[str] | None = None,
    ) -> ModelT | None:
        """Get a single record by ID.

        Args:
            id: The record ID.
            include_deleted: Whether to include soft-deleted records.
            load_relationships: List of relationship names to eagerly load.

        Returns:
            The record or None if not found.
        """
        query = select(self.model).where(self.model.id == id)  # type: ignore[attr-defined]

        # Handle soft delete
        if hasattr(self.model, "deleted_at") and not include_deleted:
            query = query.where(self.model.deleted_at.is_(None))  # type: ignore[attr-defined]

        # Eagerly load specified relationships
        if load_relationships:
            for rel_name in load_relationships:
                if hasattr(self.model, rel_name):
                    query = query.options(selectinload(getattr(self.model, rel_name)))

        result = await self.db.execute(query)
        return result.scalar_one_or_none()

    async def get_multi(
        self,
        *,
        skip: int = 0,
        limit: int = 100,
        include_deleted: bool = False,
        load_relationships: list[str] | None = None,
    ) -> Sequence[ModelT]:
        """Get multiple records with pagination.

        Args:
            skip: Number of records to skip.
            limit: Maximum number of records to return.
            include_deleted: Whether to include soft-deleted records.
            load_relationships: List of relationship names to eagerly load.

        Returns:
            List of records.
        """
        query = select(self.model)

        # Handle soft delete
        if hasattr(self.model, "deleted_at") and not include_deleted:
            query = query.where(self.model.deleted_at.is_(None))  # type: ignore[attr-defined]

        # Eagerly load specified relationships
        if load_relationships:
            for rel_name in load_relationships:
                if hasattr(self.model, rel_name):
                    query = query.options(selectinload(getattr(self.model, rel_name)))

        query = query.offset(skip).limit(limit)
        result = await self.db.execute(query)
        return result.scalars().all()

    async def count(
        self,
        *,
        include_deleted: bool = False,
    ) -> int:
        """Count total records.

        Args:
            include_deleted: Whether to include soft-deleted records.

        Returns:
            Total count of records.
        """
        query = select(func.count()).select_from(self.model)

        if hasattr(self.model, "deleted_at") and not include_deleted:
            query = query.where(self.model.deleted_at.is_(None))  # type: ignore[attr-defined]

        result = await self.db.execute(query)
        return result.scalar_one()

    async def create(
        self,
        *,
        data: CreateSchemaT,
    ) -> ModelT:
        """Create a new record.

        Args:
            data: The creation data.

        Returns:
            The created record.
        """
        # Hook: before create
        await self.before_create(data)

        # Filter out fields that don't exist on the model (e.g., relationship IDs)
        model_columns = {c.key for c in self.model.__table__.columns}
        create_data = {k: v for k, v in data.model_dump().items() if k in model_columns}

        db_obj = self.model(**create_data)
        self.db.add(db_obj)
        await self.db.commit()
        await self.db.refresh(db_obj)

        # Hook: after create
        await self.after_create(db_obj)

        return db_obj

    async def update(
        self,
        *,
        id: int,
        data: UpdateSchemaT,
    ) -> ModelT | None:
        """Update an existing record.

        Args:
            id: The record ID.
            data: The update data.

        Returns:
            The updated record or None if not found.
        """
        db_obj = await self.get(id)
        if db_obj is None:
            return None

        # Hook: before update
        await self.before_update(db_obj, data)

        # Filter out fields that don't exist on the model (e.g., relationship IDs)
        model_columns = {c.key for c in self.model.__table__.columns}
        update_data = {k: v for k, v in data.model_dump(exclude_unset=True).items() if k in model_columns}
        for field, value in update_data.items():
            setattr(db_obj, field, value)

        await self.db.commit()
        await self.db.refresh(db_obj)

        # Hook: after update
        await self.after_update(db_obj)

        return db_obj

    async def delete(
        self,
        *,
        id: int,
        soft: bool = True,
    ) -> bool:
        """Delete a record.

        Args:
            id: The record ID.
            soft: If True and model supports it, soft delete.

        Returns:
            True if deleted, False if not found.
        """
        db_obj = await self.get(id)
        if db_obj is None:
            return False

        # Hook: before delete
        await self.before_delete(db_obj)

        if soft and hasattr(db_obj, "deleted_at"):
            db_obj.deleted_at = datetime.now(timezone.utc)  # type: ignore[attr-defined]
            await self.db.commit()
        else:
            await self.db.delete(db_obj)
            await self.db.commit()

        # Hook: after delete
        await self.after_delete(db_obj)

        return True

    # Bulk operations
    async def create_many(
        self,
        *,
        data: list[CreateSchemaT],
    ) -> list[ModelT]:
        """Create multiple records in a single transaction.

        Args:
            data: List of creation data.

        Returns:
            List of created records.
        """
        if not data:
            return []

        # Filter out fields that don't exist on the model (e.g., relationship IDs)
        model_columns = {c.key for c in self.model.__table__.columns}

        db_objects = []
        for item in data:
            await self.before_create(item)
            create_data = {k: v for k, v in item.model_dump().items() if k in model_columns}
            db_obj = self.model(**create_data)
            self.db.add(db_obj)
            db_objects.append(db_obj)

        await self.db.commit()

        # Refresh all objects
        for db_obj in db_objects:
            await self.db.refresh(db_obj)
            await self.after_create(db_obj)

        return db_objects

    async def update_many(
        self,
        *,
        ids: list[int],
        data: UpdateSchemaT,
    ) -> int:
        """Update multiple records matching IDs.

        Args:
            ids: List of record IDs to update.
            data: The update data to apply to all records.

        Returns:
            Number of records updated.
        """
        if not ids:
            return 0

        # Filter out fields that don't exist on the model (e.g., relationship IDs)
        model_columns = {c.key for c in self.model.__table__.columns}
        update_data = {k: v for k, v in data.model_dump(exclude_unset=True).items() if k in model_columns}
        if not update_data:
            return 0

        query = (
            update(self.model)
            .where(self.model.id.in_(ids))  # type: ignore[attr-defined]
            .values(**update_data)
        )

        # Handle soft delete filter
        if hasattr(self.model, "deleted_at"):
            query = query.where(self.model.deleted_at.is_(None))  # type: ignore[attr-defined]

        result = await self.db.execute(query)
        await self.db.commit()

        return result.rowcount

    async def delete_many(
        self,
        *,
        ids: list[int],
        soft: bool = True,
    ) -> int:
        """Delete multiple records.

        Args:
            ids: List of record IDs to delete.
            soft: If True and model supports it, soft delete.

        Returns:
            Number of records deleted.
        """
        if not ids:
            return 0

        if soft and hasattr(self.model, "deleted_at"):
            # Soft delete: update deleted_at
            query = (
                update(self.model)
                .where(self.model.id.in_(ids))  # type: ignore[attr-defined]
                .where(self.model.deleted_at.is_(None))  # type: ignore[attr-defined]
                .values(deleted_at=datetime.now(timezone.utc))
            )
        else:
            # Hard delete
            query = delete(self.model).where(self.model.id.in_(ids))  # type: ignore[attr-defined]

        result = await self.db.execute(query)
        await self.db.commit()

        return result.rowcount

    # Lifecycle hooks - override in subclasses
    async def before_create(self, data: CreateSchemaT) -> None:  # noqa: B027
        """Hook called before creating a record."""
        ...

    async def after_create(self, obj: ModelT) -> None:  # noqa: B027
        """Hook called after creating a record."""
        ...

    async def before_update(self, obj: ModelT, data: UpdateSchemaT) -> None:  # noqa: B027
        """Hook called before updating a record."""
        ...

    async def after_update(self, obj: ModelT) -> None:  # noqa: B027
        """Hook called after updating a record."""
        ...

    async def before_delete(self, obj: ModelT) -> None:  # noqa: B027
        """Hook called before deleting a record."""
        ...

    async def after_delete(self, obj: ModelT) -> None:  # noqa: B027
        """Hook called after deleting a record."""
        ...


__all__ = ["ServiceBase", "ModelProtocol"]
