from sqlalchemy.ext.asyncio import AsyncSession

from src.db.models.app_user import AppUser
from src.db.models.role import UserRoleEnum
from src.core.errors import (
    AccessDeniedException,
    UserNotFoundException,
    ValidationException,
    UsernameConflictException,
)
from src.domain.schemas import UserCreate, UserUpdate
from src.repository.user_repository import UserRepository


class UserService:
    def __init__(self, db: AsyncSession):
        self.db = db
        self.user_repo = UserRepository(db)

    async def get_all(
            self, 
            current_user: AppUser, 
            skip: int = 0, 
            limit: int = 100,
            with_count: bool = False,
            load_orgs: bool = False,
    ) -> list[AppUser] | tuple[int, list[AppUser]]:
        if current_user.role_id != UserRoleEnum.ADMIN:
            raise AccessDeniedException()
        return await self.user_repo.get_list(
            skip=skip, 
            limit=limit, 
            with_count=with_count, 
            load_orgs=load_orgs,
        )

    async def get(
            self, 
            user_id: int, 
            current_user: AppUser | None = None, 
            load_orgs: bool = False,
    ):
        if current_user is not None and current_user.role_id != UserRoleEnum.ADMIN:
            raise AccessDeniedException()
        return await self.user_repo.get(user_id=user_id, load_orgs=load_orgs)

    async def create_user(self, user_data: UserCreate, creator: AppUser):
        if creator.role_id != UserRoleEnum.ADMIN:
            raise AccessDeniedException()

        role_id = user_data.role_id or UserRoleEnum.EXECUTOR_RF.value
        try:
            role = UserRoleEnum(role_id)
        except ValueError:
            raise ValidationException("Роль не найдена", field="role_id")
        user_data.role_id = role.value

        if await self.user_repo.get_by_username(user_data.username):
            raise UsernameConflictException()

        payload = user_data.model_dump(mode="json")
        return await self.user_repo.create(payload)

    async def update_user(self, user_id: int, user_data: UserUpdate, user: AppUser):
        if user.role_id != UserRoleEnum.ADMIN:
            raise AccessDeniedException()

        target = await self.user_repo.get(user_id)
        if not target:
            raise UserNotFoundException()

        update_payload = user_data.model_dump(mode="json", exclude_unset=True)
        previous_role_id = self._normalize_role_id(target.role_id)
        role_id = update_payload.get("role_id")
        role = None
        if role_id is not None:
            try:
                role = UserRoleEnum(role_id)
            except ValueError:
                raise ValidationException("Роль не найдена", field="role_id")

            if role_id != self._normalize_role_id(user.role_id) and user.id == user_id:
                raise ValidationException(
                    "Нельзя изменить роль самому себе", field="role_id"
                )
            update_payload["role_id"] = role.value

        if user.id == user_id and not update_payload.get('is_active', True):
            raise ValidationException(
                "Нельзя удалить себя", field="role_id"
            )

        if "username" in update_payload and update_payload["username"] != target.username:
            conflict = await self.user_repo.get_by_username(update_payload["username"])
            if conflict:
                raise UsernameConflictException()

        updated = await self.user_repo.update(
            user_id=user_id, 
            user_data=update_payload,
            load_orgs=user_data.load_orgs,
        )

        if role_id is not None and role is not None:
            had_many_ssp_role = previous_role_id in (
                UserRoleEnum.ADMIN.value,
                UserRoleEnum.EXECUTOR_DFIP.value,
            )
            has_many_ssp_role_now = role.value in (
                UserRoleEnum.ADMIN.value,
                UserRoleEnum.EXECUTOR_DFIP.value,
            )
            is_executor_to_dfip_transition = (
                previous_role_id == UserRoleEnum.EXECUTOR_RF.value
                and role.value == UserRoleEnum.EXECUTOR_DFIP.value
            )
            if (had_many_ssp_role and not has_many_ssp_role_now) or is_executor_to_dfip_transition:
                await self.user_repo.clear_many_ssp(user_id)
                if user_data.load_orgs:
                    self.db.expire(updated)
                    updated = await self.user_repo.get(
                        user_id=user_id,
                        load_orgs=user_data.load_orgs,
                    )
        return updated

    async def delete_user(self, user_id: int, user: AppUser):
        if user.role_id != UserRoleEnum.ADMIN:
            raise AccessDeniedException()
        if user.id == user_id:
            raise ValidationException(
                "Нельзя удалить себя", field="role_id"
            )
        deleted = await self.user_repo.logical_delete(user_id)
        if not deleted:
            raise UserNotFoundException()

    async def get_roles(self, current_user: AppUser):
        if current_user.role_id != UserRoleEnum.ADMIN:
            raise AccessDeniedException()
        return await self.user_repo.get_roles()

    async def get_many_ssp(self, user_id: int, current_user: AppUser):
        if current_user.role_id == UserRoleEnum.ADMIN:
            return await self.user_repo.get_many_ssp(user_id)
        if current_user.id != user_id:
            raise AccessDeniedException()
        return await self.user_repo.get_many_ssp(user_id)

    async def set_many_ssp(self, user_id: int, ssp_ids: list[int], current_user: AppUser):
        if current_user.role_id != UserRoleEnum.ADMIN:
            raise AccessDeniedException()

        user = await self.user_repo.get(user_id)
        if not user:
            raise UserNotFoundException()

        target_role_id = self._normalize_role_id(user.role_id)
        if target_role_id not in (
            UserRoleEnum.ADMIN.value,
            UserRoleEnum.EXECUTOR_DFIP.value,
            UserRoleEnum.EXECUTOR_RF.value,
        ):
            err = AccessDeniedException()
            err.description = (
                "Несколько ССП/РФ может быть только у пользовтелей с ролями EXECUTOR и ADMIN"
            )
            raise err

        curr_ssp_ids = await self.user_repo.get_many_ssp_ids(user_id)
        if target_role_id == UserRoleEnum.EXECUTOR_RF.value:
            merged_ssp_ids = set(curr_ssp_ids) | set(ssp_ids)
            if len(merged_ssp_ids) > 1:
                raise ValidationException(
                    "Для пользователя с ролью EXECUTOR_RF можно назначить только один ССП/РФ"
                )
        new_ssp_ids = [ssp_id for ssp_id in ssp_ids if ssp_id not in curr_ssp_ids]
        return await self.user_repo.set_many_ssp(user.id, new_ssp_ids)

    async def unset_many_ssp(self, user_id: int, ssp_ids: list[int], current_user: AppUser):
        if current_user.role_id != UserRoleEnum.ADMIN:
            raise AccessDeniedException()

        user = await self.user_repo.get(user_id)
        if not user:
            raise UserNotFoundException()

        target_role_id = self._normalize_role_id(user.role_id)
        if target_role_id not in (
            UserRoleEnum.ADMIN.value,
            UserRoleEnum.EXECUTOR_DFIP.value,
            UserRoleEnum.EXECUTOR_RF.value,
        ):
            err = AccessDeniedException()
            err.description = (
                "Несколько ССП/РФ может быть только у пользовтелей с ролями EXECUTOR и ADMIN"
            )
            raise err
        return await self.user_repo.unset_many_ssp(user_id, ssp_ids)

    @staticmethod
    def _normalize_role_id(role_id: int | UserRoleEnum) -> int:
        return role_id.value if isinstance(role_id, UserRoleEnum) else int(role_id)
