from typing import Optional, Tuple, Literal

from fastapi import HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from starlette import status

from app.application.lens_types_repository import LensTypesRepository
from app.application.lenses_repository import LensesRepository
from app.domain.entities.lens import LensEntity
from app.domain.models import Lens
from app.domain.models.lens import SideEnum


class LensesService:
    def __init__(self, db: AsyncSession):
        self.lenses_repository = LensesRepository(db)
        self.lens_types_repository = LensTypesRepository(db)

    async def get_all_lenses(
        self,
        page: int = 1,
        page_size: int = 10,
        search: str = None,
        sort_order: Literal["asc", "desc"] = "desc",
        tor: float = None,
        diameter: float = None,
        preset_refraction: float = None,
        periphery_toricity: float = None,
        side: str = "all",
        issued: bool = None,
        trial: float = None,
    ) -> Tuple[list[LensEntity], int]:
        skip = (page - 1) * page_size
        lenses, total_count = await self.lenses_repository.get_all(
            skip=skip,
            limit=page_size,
            search=search,
            sort_order=sort_order,
            tor=tor,
            diameter=diameter,
            preset_refraction=preset_refraction,
            periphery_toricity=periphery_toricity,
            side=side,
            issued=issued,
            trial=trial,
        )
        return [self.model_to_entity(lens) for lens in lenses], total_count

    async def get_all_not_issued_lenses(self) -> list[LensEntity]:
        lenses = await self.lenses_repository.get_all_not_issued()

        return [
            self.model_to_entity(lens)
            for lens in lenses
        ]

    async def create_lens(self, lens: LensEntity) -> Optional[LensEntity]:
        lens_type = await self.lens_types_repository.get_by_id(lens.type_id)

        if not lens_type:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='Тип линзы с таким ID не найден',
            )

        lens_model = self.entity_to_model(lens)

        await self.lenses_repository.create(lens_model)

        return self.model_to_entity(lens_model)

    async def update_lens(self, lens_id: int, lens: LensEntity) -> Optional[LensEntity]:
        lens_model = await self.lenses_repository.get_by_id(lens_id)

        if not lens_model:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Линза не найдена")

        try:
            side_enum = SideEnum(lens.side)
        except ValueError:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"Недопустимое значение стороны линзы: {lens.side}. Допустимо 'левая' или 'правая'."
            )

        lens_model.tor = lens.tor
        lens_model.trial = lens.trial
        lens_model.esa = lens.esa
        lens_model.fvc = lens.fvc
        lens_model.preset_refraction = lens.preset_refraction
        lens_model.diameter = lens.diameter
        lens_model.periphery_toricity = lens.periphery_toricity
        lens_model.side = side_enum
        lens_model.issued = lens.issued
        lens_model.type_id = lens.type_id

        await self.lenses_repository.update(lens_model)

        return self.model_to_entity(lens_model)

    async def delete_lens(self, lens_id: int) -> Optional[LensEntity]:
        lens = await self.lenses_repository.get_by_id(lens_id)

        if not lens:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Линза не найдена")

        result = await self.lenses_repository.delete(lens)

        return self.model_to_entity(result)

    @staticmethod
    def entity_to_model(lens: LensEntity) -> Lens:
        try:
            side_enum = SideEnum(lens.side)
        except ValueError:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"Недопустимое значение стороны линзы: {lens.side}. Допустимо 'левая' или 'правая'."
            )

        lens_model = Lens(
            tor=lens.tor,
            trial=lens.trial,
            esa=lens.esa,
            fvc=lens.fvc,
            preset_refraction=lens.preset_refraction,
            diameter=lens.diameter,
            periphery_toricity=lens.periphery_toricity,
            side=side_enum,
            type_id=lens.type_id,
        )

        if lens.id is not None:
            lens_model.id = lens.id

        return lens_model

    @staticmethod
    def model_to_entity(lens_model: Lens) -> LensEntity:
        return LensEntity(
            id=lens_model.id,
            tor=lens_model.tor,
            trial=lens_model.trial,
            esa=lens_model.esa,
            fvc=lens_model.fvc,
            preset_refraction=lens_model.preset_refraction,
            diameter=lens_model.diameter,
            periphery_toricity=lens_model.periphery_toricity,
            side=lens_model.side.value,
            issued=lens_model.issued,
            type_id=lens_model.type_id,
        )
