from __future__ import annotations

from datetime import datetime, timezone
from typing import Any, Generic, TypeVar

from sqlalchemy import Select, select, update
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession

from core.db.base import Base

ModelType = TypeVar("ModelType", bound=Base)


class BaseRepository(Generic[ModelType]):
    _session: AsyncSession
    _model: type[ModelType]

    def __init__(self, session: AsyncSession, model: type[ModelType]) -> None:
        self._session = session
        self._model = model

    def _deleted_at_column(self) -> Any | None:
        return getattr(self._model, "deleted_at", None)

    def _apply_soft_delete_filter(self, stmt: Select) -> Select:
        deleted_at = self._deleted_at_column()
        if deleted_at is None:
            return stmt
        return stmt.where(deleted_at.is_(None))

    async def get_by_id(self, entity_id: Any) -> ModelType | None:
        id_column = getattr(self._model, "id")
        stmt = select(self._model).where(id_column == entity_id)
        stmt = self._apply_soft_delete_filter(stmt)
        result = await self._session.execute(stmt)
        return result.scalar_one_or_none()

    async def get_one(self, *filters: Any) -> ModelType | None:
        stmt = select(self._model).where(*filters)
        stmt = self._apply_soft_delete_filter(stmt)
        result = await self._session.execute(stmt)
        return result.scalar_one_or_none()

    async def update_by_id(
        self, entity_id: Any, update_data: dict[str, Any]
    ) -> ModelType | None:
        if not update_data:
            return await self.get_by_id(entity_id)

        id_column = getattr(self._model, "id")
        stmt = update(self._model).where(id_column == entity_id)
        deleted_at = self._deleted_at_column()
        if deleted_at is not None:
            stmt = stmt.where(deleted_at.is_(None))
        stmt = stmt.values(**update_data).returning(self._model)

        try:
            result = await self._session.execute(stmt)
            await self._session.flush()
            return result.scalar_one_or_none()
        except SQLAlchemyError:
            raise

    async def soft_delete_by_id(self, entity_id: Any) -> ModelType | None:
        deleted_at = self._deleted_at_column()
        if deleted_at is None:
            raise ValueError("Soft delete is not supported for this model")

        id_column = getattr(self._model, "id")
        stmt = (
            update(self._model)
            .where(id_column == entity_id)
            .where(deleted_at.is_(None))
            .values(deleted_at=datetime.now(timezone.utc))
            .returning(self._model)
        )

        try:
            result = await self._session.execute(stmt)
            await self._session.flush()
            return result.scalar_one_or_none()
        except SQLAlchemyError:
            raise
