import os
import uuid
from http import HTTPStatus
from typing import List

import inject
from flask import jsonify

from app.extensions.utils.enum.aws_enum import S3PathEnum, S3BucketEnum, CloudFrontEnum
from app.extensions.utils.image_helper import S3Helper
from app.extensions.utils.time_helper import get_str_from_today
from core.domains.authentication.repository.auth_repository import (
    AuthenticationRepository,
)
from core.domains.plant.entity.plant_entity import PlantCategoryEntity
from core.domains.plant.repository.plant_repository import PlantRepository
from core.domains.user.dto.user_dto import (
    UploadUserProfileImgDto,
    CreateUserProfileImgDto,
    CreateUserDto,
    GetUserDto,
    UpdateUserDto,
    UpdateReceivePushTypeDto,
    GetReceivePushTypeDto,
    UpdateDeviceDto,
    UpdateFCMTokenDto,
    GetMacDto,
)
from core.domains.user.entity.user_entity import UserEntity, JwtEntity
from core.domains.user.repository.user_repository import UserRepository
from core.use_case_output import UseCaseSuccessOutput, UseCaseFailureOutput, FailureType


class UserBaseUseCase:
    @inject.autoparams()
    def __init__(
        self,
        user_repo: UserRepository,
        plant_repo: PlantRepository,
        auth_repo: AuthenticationRepository,
    ):
        self._user_repo = user_repo
        self._plant_repo = plant_repo
        self._auth_repo = auth_repo

    def _upload_user_profile_img(self, dto: CreateUserProfileImgDto) -> bool:
        res = S3Helper.upload(
            bucket=S3BucketEnum.PLANTRA_BUCKET_NAME.value,
            file_name=dto.origin_file,
            object_name=dto.object_name,
            extension=dto.extension,
        )

        return False if not res else True

    def _get_file_split_object(
        self, dto: UploadUserProfileImgDto
    ) -> CreateUserProfileImgDto:
        f, extension = os.path.splitext(dto.file.filename)
        uuid_ = str(uuid.uuid4())
        object_name = S3PathEnum.PROFILE_IMGS.value + uuid_ + extension
        path = f"{CloudFrontEnum.PLANTRA_CLOUD_FRONT_DOMAIN.value}/{object_name}"

        create_user_profile_img_dto = CreateUserProfileImgDto(
            uuid=uuid_,
            file_name=f,
            path=path,
            extension=extension,
            object_name=object_name,
            origin_file=dto.file,
        )

        return create_user_profile_img_dto

    def _check_user_nickname_validator(self, nickname: str) -> dict:
        if not nickname:
            return dict(status=False, message="empty user nickname")

        if len(nickname) > 8:
            return dict(status=False, message="user nickname over 8 char")

        if not nickname.isalnum():
            return dict(status=False, message="user nickname include special char")

        if self._user_repo.is_duplicate_nickname(nickname=nickname):
            return dict(status=False, message="duplicate user nickname")

        return dict(status=True, message="success")

    def _check_plant_nickname_validator(self, nickname: str) -> dict:
        if not nickname:
            return dict(status=False, message="empty plant nickname")

        if len(nickname) > 8:
            return dict(status=False, message="plant nickname over 8 char")

        if not nickname.isalnum():
            return dict(status=False, message="plant nickname include special char")

        if self._plant_repo.is_duplicate_plant_nickname(nickname=nickname):
            return dict(status=False, message="duplicate plant nickname")

        return dict(status=True, message="success")


class UploadUserProfileImageUseCase(UserBaseUseCase):
    def execute(
        self, dto: UploadUserProfileImgDto
    ) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        create_user_profile_img_dto = self._get_file_split_object(dto=dto)
        """
            S3 업로드 실패시에도 로직 실행
            가입단계에서 실패처리 시 유저 입장에서는 앱 사용 안할 확률이 높기 때문에
        """
        self._upload_user_profile_img(dto=create_user_profile_img_dto)

        return UseCaseSuccessOutput(value=create_user_profile_img_dto)


class CreateUserUseCase(UserBaseUseCase):
    def execute(
        self, dto: CreateUserDto
    ) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        # user nickname validator
        user_nickname_validate_result: dict = self._check_user_nickname_validator(
            nickname=dto.nickname
        )
        if not user_nickname_validate_result.get("status"):
            return UseCaseFailureOutput(
                type=user_nickname_validate_result.get("message"),
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        # plant nickname validator
        plant_nickname_validate_result: dict = self._check_plant_nickname_validator(
            nickname=dto.plant_name
        )
        if not plant_nickname_validate_result.get("status"):
            return UseCaseFailureOutput(
                type=plant_nickname_validate_result.get("message"),
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        # start_growing_date validator
        if dto.start_growing_date > get_str_from_today():
            return UseCaseFailureOutput(
                type="start_growing_date has passed",
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        # 기존 유저인 경우
        found_user_id: int | None = self._user_repo.find_user_id_by_provider_id(
            provider_id=dto.provider_id, provider=dto.provider
        )

        if found_user_id:
            user: UserEntity = self._user_repo.get_user_by_id(found_user_id)
            if user.is_out:
                return UseCaseFailureOutput(
                    type="Invalid user: user is out",
                    message=FailureType.UNAUTHORIZED_ERROR,
                )
            if not user.is_available:
                return UseCaseFailureOutput(
                    type="Invalid user: user is not available",
                    message=FailureType.UNAUTHORIZED_ERROR,
                )

            self._auth_repo.update_jwt(user.id)
            token_info = self._auth_repo.get_jwt(user.id)

            # login_uuid, current_connection_time update
            self._user_repo.update_user_uuid(user_id=user.id, login_uuid=dto.login_uuid)
            self._user_repo.update_current_connection_time(user_id=user.id)
        else:
            user_id = self._user_repo.create_user(dto=dto)

            self._user_repo.create_notification_token(user_id=user_id)
            self._user_repo.create_device(dto=dto, user_id=user_id)
            self._user_repo.create_receive_push_type(user_id=user_id)
            self._user_repo.create_plant_profile(dto=dto, user_id=user_id)
            self._user_repo.create_app_agree_terms(dto=dto, user_id=user_id)
            self._user_repo.create_jwt(user_id=user_id)

            self._user_repo.commit_transaction()

            if dto.file_path:
                self._user_repo.create_user_profile(dto=dto, user_id=user_id)

            token_info = self._auth_repo.get_jwt(user_id)

        if token_info:
            result = jsonify(access_token=token_info.access_token)
            return UseCaseSuccessOutput(value=result)
        else:
            return UseCaseFailureOutput(
                type="get JWT Failed",
                message=FailureType.INTERNAL_ERROR,
                code=HTTPStatus.INTERNAL_SERVER_ERROR,
            )


class UpdateUserUseCase(UserBaseUseCase):
    def execute(
        self, dto: UpdateUserDto
    ) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        # start_growing_date validator
        if dto.start_growing_date > get_str_from_today():
            return UseCaseFailureOutput(
                type="start_growing_date has passed",
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        self._plant_repo.update_plant_profile(dto=dto)
        self._user_repo.update_user_nickname(
            user_id=dto.user_id, nickname=dto.user_nickname
        )

        if dto.file_path:
            if not self._user_repo.is_exist_user_profile(user_id=dto.user_id):
                self._user_repo.create_user_profile(dto=dto, user_id=dto.user_id)
            else:
                self._user_repo.update_user_profile(dto=dto)

        return UseCaseSuccessOutput()


class GetUserUseCase(UserBaseUseCase):
    def execute(self, dto: GetUserDto) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        user: UserEntity | None = self._user_repo.get_user_by_id(id=dto.user_id)
        plant_categories: List[
            PlantCategoryEntity
        ] = self._plant_repo.get_plant_categories()

        if not user:
            return UseCaseFailureOutput(
                type="user_id",
                message=FailureType.NOT_FOUND_ERROR,
                code=HTTPStatus.NOT_FOUND,
            )

        return UseCaseSuccessOutput(
            value=dict(user=user, plant_categories=plant_categories)
        )


class GetReceivePushTypeUseCase(UserBaseUseCase):
    def execute(
        self, dto: GetReceivePushTypeDto
    ) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        result = self._user_repo.get_receive_push_type(user_id=dto.user_id)

        return UseCaseSuccessOutput(value=result)


class UpdateReceivePushTypeUseCase(UserBaseUseCase):
    def execute(
        self, dto: UpdateReceivePushTypeDto
    ) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        self._user_repo.update_receive_push_type(dto=dto)

        return UseCaseSuccessOutput()


class UpdateDeviceUseCase(UserBaseUseCase):
    def execute(
        self, dto: UpdateDeviceDto
    ) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        self._user_repo.update_device(dto=dto)

        return UseCaseSuccessOutput()


class UpdateFCMTokenUseCase(UserBaseUseCase):
    def execute(
        self, dto: UpdateFCMTokenDto
    ) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        if not self._user_repo.is_exist_fcm_token(user_id=dto.user_id):
            self._user_repo.create_fcm_token(
                user_id=dto.user_id, fcm_token=dto.fcm_token
            )
        else:
            self._user_repo.update_fcm_token(
                user_id=dto.user_id, fcm_token=dto.fcm_token
            )

        return UseCaseSuccessOutput()


class GetMacUseCase(UserBaseUseCase):
    def execute(self, dto: GetMacDto) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        hw_mac: str | None = self._user_repo.get_mac_address(user_id=dto.user_id)

        return UseCaseSuccessOutput(value=hw_mac)
