import importlib
import json
from typing import Type

from api.auth.keycloak_client import Keycloak
from api.broadcast import broadcast
from core.database.abc.data_access_object_abc import DataAccessObjectABC
from core.database.abc.db_model_dao_abc import DbModelDaoABC
from core.logger import Logger
from core.string import first_to_lower
from data.schemas.administration.user_dao import userDao
from data.schemas.public.user_invitation_dao import userInvitationDao

logger = Logger("DataPrivacy")


class DataPrivacyService:

    @staticmethod
    def _dynamic_import_dao(dao_class: Type[DataAccessObjectABC]):
        """
        Dynamically import a DAO class and its instance.
        :param dao_class: The DAO class to be imported.
        :return: The DAO instance.
        """
        module = importlib.import_module(dao_class.__module__)
        dao_instance = getattr(
            module, first_to_lower(first_to_lower(dao_class.__name__))
        )
        return dao_instance

    @classmethod
    async def _collect_user_relevant_dao(cls):
        """
        Collect all DAO classes that are relevant for data privacy.
        :return: List of relevant DAO classes.
        """
        # This method should return a list of DAOs that are relevant for data privacy
        # For example, it could return a list of DAOs that contain user data
        classes: list[DataAccessObjectABC] = [
            cls._dynamic_import_dao(dao) for dao in DbModelDaoABC.__subclasses__()
        ]
        return [x for x in classes if x.has_attribute("user_id")]

    @classmethod
    async def export_user_data(cls, user_id: int):
        """
        Export user data from the database.
        :param user_id: ID of the user whose data is to be exported.
        :return: User data in a structured format.
        """
        # Logic to export user data
        user = await userDao.find_by_id(user_id)
        if user is None:
            raise ValueError("User not found")

        collected_data = {"user": userDao.to_dict(await userDao.find_by_id(user_id))}

        daos = await cls._collect_user_relevant_dao()
        for dao in daos:
            data = await dao.find_by([{"userid": user_id}])
            collected_data[first_to_lower(type(dao).__name__.replace("Dao", "s"))] = [
                dao.to_dict(x) for x in data
            ]

        return json.dumps(collected_data, default=str)

    @staticmethod
    async def anonymize_user(user_id: int):
        """
        Anonymize user data in the database.
        :param user_id: ID of the user to be anonymized.
        """
        user = await userDao.find_by_id(user_id)
        if user is None:
            raise ValueError("User not found")

        keycloak_id = user.keycloak_id

        # Anonymize internal data
        user_invitations = await userInvitationDao.find_by(
            [{"email": {"equal": user.email}}]
        )
        for user_invitation in user_invitations:
            user_invitation.email = "ANONYMOUS"
            user_invitation.deleted = True
            await userInvitationDao.update(user_invitation)

        await user.anonymize()
        await userDao.delete(user)

        # Anonymize external data
        try:
            Keycloak.admin.delete_user(keycloak_id)
            await broadcast.publish("userLogout", user.id)
        except Exception as e:
            logger.error(f"Failed to anonymize external data for user {user_id}", e)
            raise ValueError("Failed to anonymize external data") from e

        return True

    @classmethod
    async def delete_user_data(cls, user_id: int):
        """
        Delete user data from the database.
        :param user_id: ID of the user whose data is to be deleted.
        """
        user = await userDao.find_by_id(user_id)
        if user is None:
            raise ValueError("User not found")

        keycloak_id = user.keycloak_id

        daos = await cls._collect_user_relevant_dao()
        for dao in daos:
            data = await dao.find_by([{"userid": user_id}])
            try:
                await dao.delete_many(data, hard_delete=True)
            except Exception as e:
                logger.error(f"Failed to delete data for user {user_id}", e)
                raise ValueError("Failed to delete data") from e

        try:
            await userDao.delete(user)
        except Exception as e:
            logger.error(f"Failed to delete user {user_id}", e)
            raise ValueError("Failed to delete user") from e

        # Delete external data
        try:
            Keycloak.admin.delete_user(keycloak_id)
        except Exception as e:
            logger.error(f"Failed to delete external data for user {user_id}", e)
            raise ValueError("Failed to delete external data") from e

        return True
