from flask_jwt_extended import create_access_token, create_refresh_token
from sqlalchemy import exc, func, or_
from sqlalchemy.orm import joinedload

from app.extensions.database import session
from app.extensions.utils.log_helper import logger_
from app.extensions.utils.time_helper import get_str_from_today, get_server_timestamp
from app.persistence.model import (
    DeviceModel,
    UserModel,
    NotificationTokenModel,
    ReceivePushTypeModel,
    PlantProfileModel,
    UserProfileModel,
    JwtModel,
    AppAgreeTermsModel,
)
from core.domains.user.dto.user_dto import (
    CreateUserDto,
    UpdateUserDto,
    UpdateReceivePushTypeDto,
    UpdateDeviceDto,
)
from core.domains.user.entity.user_entity import UserEntity
from core.domains.user.enum.user_enum import UserGroupEnum
from core.exceptions import NotUniqueErrorException

logger = logger_.getLogger(__name__)


class UserRepository:
    def commit_transaction(self) -> None:
        try:
            session.commit()
        except exc.IntegrityError as e:
            logger.error(f"[UserRepository][commit_transaction] error : {e}")
            session.rollback()
            raise NotUniqueErrorException

    def get_user_by_id(self, id: int) -> UserEntity | None:
        query = (
            session.query(UserModel)
            .options(joinedload(UserModel.device, innerjoin=True))
            .options(joinedload(UserModel.notification_token, innerjoin=True))
            .options(joinedload(UserModel.receive_push_type, innerjoin=True))
            .options(joinedload(UserModel.jwt, innerjoin=True))
            .options(joinedload(UserModel.plant_profile, innerjoin=True))
            .options(joinedload(UserModel.user_profile))
            .options(joinedload(UserModel.blacklists))
            .filter_by(id=id)
        )
        query_set = query.first()

        if not query_set:
            return None

        return query_set.to_entity()

    def get_plant_id_by_mac(self, sensormac: str) -> dict | None:
        query = (
            session.query(DeviceModel)
            .options(joinedload(DeviceModel.users, innerjoin=True))
            .options(joinedload("users.plant_profile", innerjoin=True))
            .options(joinedload("users.notification_token", innerjoin=True))
            .filter(DeviceModel.hw_mac == sensormac)
        )
        query_set = query.first()

        if not query_set:
            return None

        return dict(
            plant_id=query_set.users.plant_profile.id,
            plant_nickname=query_set.users.plant_profile.name,
            fcm_token=query_set.users.notification_token.token,
            user_id=query_set.users.id,
        )

    def get_user_nickname(self, user_id: int) -> str:
        query = session.query(UserModel).filter(UserModel.id == user_id)
        query_set = query.first()

        return query_set.nickname

    def is_duplicate_nickname(self, nickname: str) -> bool:
        query = session.query(
            UserModel.query.filter(UserModel.nickname == nickname).exists()
        )
        query_set = query.scalar()
        return query_set

    def is_exist_user_profile(self, user_id: int) -> bool:
        query = session.query(
            UserProfileModel.query.filter(UserProfileModel.user_id == user_id).exists()
        )
        query_set = query.scalar()
        return query_set

    def is_check_active_user(self, user_id: int) -> bool:
        query = session.query(
            UserModel.query.filter(
                UserModel.id == user_id,
                or_(UserModel.is_available == False, UserModel.is_out == True),
            ).exists()
        )
        query_set = query.scalar()
        return query_set

    def create_user(self, dto: CreateUserDto) -> int:
        try:
            model = UserModel(
                email=dto.email,
                join_date=get_str_from_today(),
                provider=dto.provider,
                provider_id=dto.provider_id,
                group=UserGroupEnum.USER.value,
                nickname=dto.nickname,
                is_out=False,
                is_available=True,
                uuid=str(dto.login_uuid),
            )
            session.add(model)
            session.flush()

            return model.id
        except exc.IntegrityError as e:
            logger.error(f"[UserRepository][create_user] error : {e}")
            session.rollback()
            raise NotUniqueErrorException

    def create_notification_token(self, user_id: int) -> None:
        try:
            model = NotificationTokenModel(user_id=user_id,)
            session.add(model)
            session.flush()
        except exc.IntegrityError as e:
            logger.error(
                f"[UserRepository][create_notification_token] user_id : {user_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def create_device(self, dto: CreateUserDto, user_id: int) -> None:
        try:
            model = DeviceModel(user_id=user_id, mobile_os=dto.mobile_os)
            session.add(model)
            session.flush()
        except exc.IntegrityError as e:
            logger.error(
                f"[UserRepository][create_device] user_id : {user_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def create_receive_push_type(self, user_id: int) -> None:
        try:
            model = ReceivePushTypeModel(
                user_id=user_id, is_comment=True, is_like=True, is_incubator=True,
            )
            session.add(model)
            session.flush()
        except exc.IntegrityError as e:
            logger.error(
                f"[UserRepository][create_receive_push_type] user_id : {user_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def create_plant_profile(self, dto: CreateUserDto, user_id: int) -> None:
        try:
            model = PlantProfileModel(
                user_id=user_id,
                name=dto.plant_name,
                start_growing_date=dto.start_growing_date,
                plant_category_id=dto.plant_category_id,
            )
            session.add(model)
            session.flush()
        except exc.IntegrityError as e:
            logger.error(
                f"[UserRepository][create_plant_profile] user_id : {user_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def create_user_profile(
        self, dto: CreateUserDto | UpdateUserDto, user_id: int
    ) -> None:
        try:
            model = UserProfileModel(
                user_id=user_id,
                uuid=dto.file_uuid,
                file_name=dto.file_name,
                path=dto.file_path,
                extension=dto.file_extension,
            )
            session.add(model)
            session.commit()
        except exc.IntegrityError as e:
            logger.error(
                f"[UserRepository][create_user_profile] user_id : {user_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def create_jwt(self, user_id: int) -> None:
        try:
            model = JwtModel(
                user_id=user_id,
                access_token=create_access_token(identity=user_id),
                refresh_token=create_refresh_token(identity=user_id),
            )
            session.add(model)
            session.flush()
        except exc.IntegrityError as e:
            logger.error(
                f"[UserRepository][create_jwt] user_id : {user_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def create_app_agree_terms(self, dto: CreateUserDto, user_id: int) -> None:
        try:
            model = AppAgreeTermsModel(
                user_id=user_id,
                private_user_info_yn=dto.private_user_info_yn,
                required_terms_yn=dto.required_terms_yn,
                receive_marketing_yn=dto.receive_marketing_yn,
                update_receive_marketing_at=func.now()
                if dto.receive_marketing_yn
                else None,
            )
            session.add(model)
            session.flush()
        except exc.IntegrityError as e:
            logger.error(
                f"[UserRepository][create_app_agree_terms] user_id : {user_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def update_user_profile(self, dto: UpdateUserDto) -> None:
        try:
            session.query(UserProfileModel).filter_by(user_id=dto.user_id).update(
                {
                    "uuid": dto.file_uuid,
                    "file_name": dto.file_name,
                    "path": dto.file_path,
                    "extension": dto.file_extension,
                }
            )
            session.commit()
        except exc.IntegrityError as e:
            logger.error(
                f"[UserRepository][update_user_profile] user_id : {dto.user_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def get_receive_push_type(self, user_id: int) -> int | None:
        query = session.query(ReceivePushTypeModel).filter_by(user_id=user_id)
        query_set = query.first()

        if not query_set:
            return None

        return query_set.to_entity()

    def update_receive_push_type(self, dto: UpdateReceivePushTypeDto) -> None:
        try:
            session.query(ReceivePushTypeModel).filter_by(user_id=dto.user_id).update(
                {
                    "is_comment": dto.is_comment,
                    "is_like": dto.is_like,
                    "is_incubator": dto.is_incubator,
                }
            )
            session.commit()
        except exc.IntegrityError as e:
            logger.error(
                f"[UserRepository][update_receive_push_type] user_id : {dto.user_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def update_device(self, dto: UpdateDeviceDto) -> None:
        try:
            session.query(DeviceModel).filter_by(hw_mac=dto.hw_mac).update(
                {"hw_mac": None}
            )

            session.query(DeviceModel).filter_by(user_id=dto.user_id).update(
                {"hw_mac": dto.hw_mac,}
            )
            session.commit()
        except exc.IntegrityError as e:
            logger.error(
                f"[UserRepository][update_device] user_id : {dto.user_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def update_user_is_out(self, user_id: int, status: bool) -> None:
        try:
            session.query(UserModel).filter_by(id=user_id).update(
                {"is_out": status, "updated_at": get_server_timestamp()}
            )
            session.commit()
        except Exception as e:
            session.rollback()
            logger.error(
                f"[UserRepository][update_user_is_out] user_id : {user_id} error : {e}"
            )
            raise Exception(e)

    def update_current_connection_time(self, user_id: int) -> None:
        """
            최근 접속 일자
        """
        try:
            session.query(UserModel).filter_by(id=user_id).update(
                {"current_connection_time": get_server_timestamp()}
            )
            session.commit()
        except Exception as e:
            session.rollback()
            logger.error(
                f"[UserRepository][update_current_connection_time] user_id : {user_id} "
                f"error : {e}"
            )

    def find_user_id_by_provider_id(
        self, provider_id: str, provider: str
    ) -> int | None:
        query = session.query(UserModel).filter(
            UserModel.provider == provider,
            UserModel.provider_id == func.crypt(provider_id, UserModel.provider_id),
        )
        user: UserModel | None = query.first()

        if not user:
            return None

        return user.id

    def get_crypt_provider_id(self, provider_id: str) -> str:
        query = session.query(func.crypt(provider_id, func.gen_salt("bf")))

        result = query.first()

        return "".join(result)

    # def find_user_id_by_provider_id(
    #     self, provider_id: str, provider: str
    # ) -> int | None:
    #     query = session.query(UserModel).filter_by(
    #         provider_id=provider_id, provider=provider
    #     )
    #     user: UserModel | None = query.first()
    #
    #     if not user:
    #         return None
    #
    #     return user.id

    def is_exist_fcm_token(self, user_id: int) -> bool:
        query = session.query(
            NotificationTokenModel.query.filter(
                NotificationTokenModel.user_id == user_id
            ).exists()
        )
        query_set = query.scalar()
        return query_set

    def update_fcm_token(self, user_id: int, fcm_token: str) -> None:
        try:
            session.query(NotificationTokenModel).filter_by(user_id=user_id).update(
                {"token": fcm_token}
            )
            session.commit()
        except Exception as e:
            session.rollback()
            logger.error(
                f"[UserRepository][update_fcm_token] user_id : {user_id} "
                f"error : {e}"
            )

    def create_fcm_token(self, user_id: int, fcm_token: str) -> None:
        try:
            model = NotificationTokenModel(user_id=user_id, token=fcm_token,)
            session.add(model)
            session.commit()
        except exc.IntegrityError as e:
            logger.error(
                f"[UserRepository][create_fcm_token] user_id : {user_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def update_user_nickname(self, user_id: int, nickname: str) -> None:
        try:
            session.query(UserModel).filter_by(id=user_id).update(
                {"nickname": nickname,}
            )
            session.commit()
        except exc.IntegrityError as e:
            logger.error(
                f"[UserRepository][update_user_nickname] user_id : {user_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def update_user_uuid(self, user_id: int, login_uuid: str) -> None:
        """
            기존 사용자 로그인시 UUID를 갱신한다
        """
        try:
            session.query(UserModel).filter_by(id=user_id).update(
                {"uuid": login_uuid,}
            )
            session.commit()
        except Exception as e:
            session.rollback()
            logger.error(f"[UserRepository][update_user_uuid] error : {e}")

    def get_mac_address(self, user_id: int) -> str | None:
        query = session.query(DeviceModel).filter(DeviceModel.user_id == user_id)
        query_set = query.first()

        if not query_set:
            return None

        return query_set.hw_mac
