from abc import ABCMeta, abstractmethod
from pathlib import Path

from aiocache import Cache, cached  # type: ignore
from sqlalchemy import select

from server.infra.db import AsyncDB
from server.modules.descriptions.domain import Breed
from server.modules.descriptions.repository import models


class ACharactersRepository(metaclass=ABCMeta):
    @abstractmethod
    async def get_characters(self, kind: str) -> list[Breed]:
        pass

    @abstractmethod
    async def get_character(self, alias: str) -> Breed | None:
        pass


class CharactersRepository(ACharactersRepository):
    def __init__(self):
        pass

    @cached(ttl=60, cache=Cache.MEMORY)
    async def get_characters(self, kind: str) -> list[Breed]:
        breed_dir = Path("server/modules/descriptions/repository/breed_descriptions")
        breeds: list[Breed] = []

        # Идем по каждому текстовому файлу с описанием породы
        for breed_file in breed_dir.glob("*.txt"):
            breed_name = breed_file.stem  # имя файла без расширения - название породы
            description = breed_file.read_text(encoding="utf-8")  # читаем описание из файла
            breeds.append(
                Breed(
                    id=breed_name,
                    name=breed_name.replace("_", " "),
                    alias=breed_file.stem,
                    description=description.strip(),
                )
            )
        breeds.sort(key=lambda b: b.name)
        return breeds

    async def get_character(self, alias: str) -> Breed | None:
        breeds = await self.get_characters()
        data = [b for b in breeds if b.alias == alias]
        if len(data) == 0:
            return None
        return data[0]


class PGCharactersRepository(ACharactersRepository):
    _db: AsyncDB

    def __init__(self, db: AsyncDB):
        self._db = db

    # ───────────────────────────────────────────────────────────────────── #
    # 8️⃣  Кешируемый метод, который возвращает **все** породы
    # ───────────────────────────────────────────────────────────────────── #
    @cached(ttl=60, cache=Cache.MEMORY)  # 1‑мин. кеш
    async def get_characters(self, kind: str) -> list[Breed]:
        """
        Читает данные из таблицы `beerds.beerds` и преобразует каждую строку
        в экземпляр `Breed`.  Поле `signs` игнорируется – в `Breed` его нет.
        """

        async with self._db.async_session() as session:
            # Писем SELECT‑запрос (получаем все строки)
            stmt = select(  # type: ignore
                models.Beerds.id,
                models.Beerds.name,
                models.Beerds.alias,
                models.Beerds.descriptions,
            ).where(models.Beerds.kind == kind)
            result = await session.execute(stmt)
            rows = result.fetchall()

        # Конвертируем в Breed
        breeds: list[Breed] = [
            Breed(
                id=str(row.id),
                name=row.name.strip(),
                alias=row.alias.strip(),
                description=None,
            )
            for row in rows
        ]

        # Сортируем по имени, как было в файле‑реализации
        breeds.sort(key=lambda b: b.name.lower())
        return breeds

    # ───────────────────────────────────────────────────────────────────── #
    # 9️⃣  Получить конкретную породу по псевдониму
    # ───────────────────────────────────────────────────────────────────── #
    async def get_character(self, alias: str) -> Breed | None:
        """
        Быстрый запрос без получения всех пород.  Если результат
        пустой – возвращаем `None`.
        """

        async with self._db.async_session() as session:
            stmt = (
                select(  # type: ignore
                    models.Beerds.id,
                    models.Beerds.name,
                    models.Beerds.alias,
                    models.Beerds.descriptions,
                )
                .where(models.Beerds.alias == alias)
                .limit(1)
            )
            result = await session.execute(stmt)
            row = result.fetchone()

        if row is None:  # pragma: no cover
            return None

        return Breed(
            id=str(row.id),
            name=row.name.strip(),
            alias=row.alias.strip(),
            description=row.descriptions.strip(),
        )
