import datetime
import json
import os
import uuid
from http import HTTPStatus
from math import trunc
from typing import List

import inject
from pytz import timezone

from app.extensions.push.notification import FCM
from app.extensions.utils.enum.aws_enum import S3PathEnum, CloudFrontEnum, S3BucketEnum
from app.extensions.utils.image_helper import S3Helper
from app.extensions.utils.time_helper import get_server_timestamp
from core.domains.community.dto.community_dto import (
    CreatePostDto,
    CreatePostAttachmentDto,
    UpdatePostDto,
    DeletePostDto,
    GetPostDto,
    UpdatePostLikeStatusDto,
    CreateCommentDto,
    UpdateCommentDto,
    DeleteCommentDto,
    UpdateCommentLikeStatusDto,
    GetFeedsDto,
    GetPushFeedDto,
)
from core.domains.community.entity.community_entity import (
    PostCategoryEntity,
    PostEntity,
    CommentEntity,
    FeedEntity,
    NoticeEntity,
)
from core.domains.community.repository.community_repository import CommunityRepository
from core.domains.notification.dto.notification_dto import CreateNotificationDto
from core.domains.notification.enum.notification_enum import (
    NotificationTopicEnum,
    NotificationStatusEnum,
)
from core.domains.notification.repository.notification_repository import (
    NotificationRepository,
)
from core.domains.user.repository.user_repository import UserRepository
from core.use_case_output import UseCaseSuccessOutput, UseCaseFailureOutput, FailureType


class CommunityBaseUseCase:
    @inject.autoparams()
    def __init__(
        self,
        community_repo: CommunityRepository,
        user_repo: UserRepository,
        notification_repo: NotificationRepository,
    ):
        self._community_repo = community_repo
        self._notification_repo = notification_repo
        self._user_repo = user_repo

    def _push_notification(
        self,
        user_id: int,
        title: str | None,
        body: str,
        token: str,
        topic: str,
        data: dict,
    ) -> CreateNotificationDto:
        if FCM.send_message(title=title, body=body, token=token, data=data):
            status = NotificationStatusEnum.SUCCESS.value
        else:
            status = NotificationStatusEnum.FAILURE.value

        return CreateNotificationDto(
            user_id=user_id,
            token=token,
            topic=topic,
            status=status,
            message=json.dumps(dict(title=title, body=body), ensure_ascii=False),
        )

    def _upload_post_attachments(self, dtos: List[CreatePostAttachmentDto]) -> None:
        for dto in dtos:
            res = S3Helper.upload(
                bucket=S3BucketEnum.PLANTRA_BUCKET_NAME.value,
                file_name=dto.origin_file,
                object_name=dto.object_name,
                extension=dto.extension,
            )

            if res:
                dto.is_upload = True

    def _get_file_split_object(
        self, dto: CreatePostDto | UpdatePostDto, post_id: int
    ) -> List[CreatePostAttachmentDto]:
        results = list()
        for file in dto.files:
            f, extension = os.path.splitext(file.filename)
            uuid_ = str(uuid.uuid4())
            object_name = S3PathEnum.POST_IMGS.value + uuid_ + extension
            path = f"{CloudFrontEnum.PLANTRA_CLOUD_FRONT_DOMAIN.value}/{object_name}"

            create_post_attachment_dto = CreatePostAttachmentDto(
                post_id=post_id,
                uuid=uuid_,
                file_name=f,
                path=path,
                extension=extension,
                object_name=object_name,
                origin_file=file,
                is_upload=False,
            )
            results.append(create_post_attachment_dto)

        return results

    def _check_body_validator(self, body: str) -> dict:
        if not body:
            return dict(status=False, message="empty body")

        if len(body) > 200:
            return dict(status=False, message="over 200 char")

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

    def _has_banned_word(self, body: str) -> bool:
        """
        금지어가 존재하는지 검사하는 함수
        """
        banned_words = self._community_repo.get_banned_word()
        for banned_word in banned_words:
            if banned_word in body:
                return True

        return False


class GetPostCategoryUseCase(CommunityBaseUseCase):
    def execute(self) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        results: List[PostCategoryEntity] = self._community_repo.get_post_categories()
        return UseCaseSuccessOutput(value=results)


class CreatePostUseCase(CommunityBaseUseCase):
    def execute(
        self, dto: CreatePostDto
    ) -> UseCaseSuccessOutput | UseCaseFailureOutput:

        if self._user_repo.is_check_active_user(user_id=dto.user_id):
            return UseCaseFailureOutput(
                type="inactive user",
                message=FailureType.UNAUTHORIZED_ERROR,
                code=HTTPStatus.UNAUTHORIZED,
            )

        # body validator
        body_validate_result: dict = self._check_body_validator(body=dto.body)
        if not body_validate_result.get("status"):
            return UseCaseFailureOutput(
                type=body_validate_result.get("message"),
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        if len(dto.files) > 5:
            return UseCaseFailureOutput(
                type="over 5 picture",
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        if self._has_banned_word(body=dto.body):
            return UseCaseFailureOutput(
                type="banned word",
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        post_id: int = self._community_repo.create_post(dto=dto)

        if dto.files:
            create_post_attachment_dtos: List[
                CreatePostAttachmentDto
            ] = self._get_file_split_object(dto=dto, post_id=post_id)
            self._upload_post_attachments(dtos=create_post_attachment_dtos)

            for create_post_attachment_dto in create_post_attachment_dtos:
                if not create_post_attachment_dto.is_upload:
                    continue

                self._community_repo.create_post_attachment(
                    dto=create_post_attachment_dto
                )

        self._community_repo.commit_transaction()

        return UseCaseSuccessOutput()


class UpdatePostUseCase(CommunityBaseUseCase):
    def execute(
        self, dto: UpdatePostDto
    ) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        if self._user_repo.is_check_active_user(user_id=dto.user_id):
            return UseCaseFailureOutput(
                type="inactive user",
                message=FailureType.UNAUTHORIZED_ERROR,
                code=HTTPStatus.UNAUTHORIZED,
            )

        post: PostEntity | None = self._community_repo.get_post_by_id(id=dto.post_id)

        if dto.user_id != post.user_id:
            return UseCaseFailureOutput(
                type="not permission",
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        # body validator
        body_validate_result: dict = self._check_body_validator(body=dto.body)
        if not body_validate_result.get("status"):
            return UseCaseFailureOutput(
                type=body_validate_result.get("message"),
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        if len(dto.files) > 5:
            return UseCaseFailureOutput(
                type="over 5 picture",
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        if self._has_banned_word(body=dto.body):
            return UseCaseFailureOutput(
                type="banned word",
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        self._community_repo.update_post(dto=dto)

        if dto.files:
            create_post_attachment_dtos: List[
                CreatePostAttachmentDto
            ] = self._get_file_split_object(dto=dto, post_id=dto.post_id)
            self._upload_post_attachments(dtos=create_post_attachment_dtos)

            for create_post_attachment_dto in create_post_attachment_dtos:
                if not create_post_attachment_dto.is_upload:
                    continue

                self._community_repo.create_post_attachment(
                    dto=create_post_attachment_dto
                )

        if dto.delete_files:
            self._community_repo.delete_post_attachments(delete_files=dto.delete_files)

        self._community_repo.commit_transaction()
        return UseCaseSuccessOutput()


class DeletePostUseCase(CommunityBaseUseCase):
    def execute(
        self, dto: DeletePostDto
    ) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        post: PostEntity | None = self._community_repo.get_post_by_id(id=dto.post_id)

        if dto.user_id != post.user_id:
            return UseCaseFailureOutput(
                type="not permission",
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        self._community_repo.delete_post(post_id=dto.post_id)

        return UseCaseSuccessOutput()


class GetPostUseCase(CommunityBaseUseCase):
    def execute(self, dto: GetPostDto) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        post: PostEntity | None = self._community_repo.get_post_by_id(id=dto.post_id)
        post_categories: List[
            PostCategoryEntity
        ] = self._community_repo.get_post_categories()
        return UseCaseSuccessOutput(
            value=dict(post=post, post_categories=post_categories)
        )


class UpdatePostLikeStatusUseCase(CommunityBaseUseCase):
    def execute(
        self, dto: UpdatePostLikeStatusDto
    ) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        if not self._community_repo.is_post_like_status(dto=dto):
            self._community_repo.create_post_like_status(dto=dto)
            self._community_repo.plus_like_count_for_post(dto=dto)
        else:
            self._community_repo.update_post_like_status(dto=dto)
            if dto.is_liked:
                self._community_repo.plus_like_count_for_post(dto=dto)
            else:
                self._community_repo.minus_like_count_for_post(dto=dto)

        if dto.is_liked:
            # 좋아요 push
            # push target 대상 정보
            post_entity: PostEntity = self._community_repo.get_post_by_id(
                id=dto.post_id
            )
            fcm_token: str | None = self._notification_repo.get_fcm_token(
                post_entity.user_id
            )

            # 유저 닉네임 조회
            user_nickname: str = self._user_repo.get_user_nickname(user_id=dto.user_id)

            if fcm_token and post_entity.user_id != dto.user_id:
                # push notification
                body = f"[{user_nickname}]님이 게시물을 좋아합니다"
                data = dict(
                    post_id=dto.post_id,
                    user_id=post_entity.user_id,
                    topic=NotificationTopicEnum.LIKE.value,
                )
                create_notification_dto: CreateNotificationDto = self._push_notification(
                    user_id=post_entity.user_id,
                    title=None,
                    body=body,
                    token=fcm_token,
                    topic=NotificationTopicEnum.LIKE.value,
                    data=data,
                )
                # notification history 저장
                self._notification_repo.create_notification(dto=create_notification_dto)

        return UseCaseSuccessOutput()


class CreateCommentUseCase(CommunityBaseUseCase):
    def execute(
        self, dto: CreateCommentDto
    ) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        if self._user_repo.is_check_active_user(user_id=dto.user_id):
            return UseCaseFailureOutput(
                type="inactive user",
                message=FailureType.UNAUTHORIZED_ERROR,
                code=HTTPStatus.UNAUTHORIZED,
            )

        # body validator
        body_validate_result: dict = self._check_body_validator(body=dto.body)
        if not body_validate_result.get("status"):
            return UseCaseFailureOutput(
                type=body_validate_result.get("message"),
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        if self._has_banned_word(body=dto.body):
            return UseCaseFailureOutput(
                type="banned word",
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        comment_id: int = self._community_repo.create_comment(dto=dto)

        # 댓글 작성 push
        # push target 대상 정보
        post_entity: PostEntity = self._community_repo.get_post_by_id(id=dto.post_id)
        fcm_token: str | None = self._notification_repo.get_fcm_token(
            post_entity.user_id
        )

        # 유저 닉네임 조회
        user_nickname: str = self._user_repo.get_user_nickname(user_id=dto.user_id)

        results: List[
            CommentEntity
        ] | None = self._community_repo.get_comment_by_post_id(
            user_id=dto.user_id, post_id=dto.post_id, writer_id=post_entity.user_id
        )

        if fcm_token and post_entity.user_id != dto.user_id:
            # push notification
            body = f"[{user_nickname}]님이 게시물에 댓글을 작성하였습니다"
            data = dict(
                post_id=dto.post_id,
                comment_id=comment_id,
                user_id=post_entity.user_id,
                topic=NotificationTopicEnum.COMMENT.value,
            )
            create_notification_dto: CreateNotificationDto = self._push_notification(
                user_id=post_entity.user_id,
                title=None,
                body=body,
                token=fcm_token,
                topic=NotificationTopicEnum.LIKE.value,
                data=data,
            )
            # notification history 저장
            self._notification_repo.create_notification(dto=create_notification_dto)

        return UseCaseSuccessOutput(value=results)


class UpdateCommentUseCase(CommunityBaseUseCase):
    def execute(
        self, dto: UpdateCommentDto
    ) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        if self._user_repo.is_check_active_user(user_id=dto.user_id):
            return UseCaseFailureOutput(
                type="inactive user",
                message=FailureType.UNAUTHORIZED_ERROR,
                code=HTTPStatus.UNAUTHORIZED,
            )

        comment: CommentEntity | None = self._community_repo.get_comment_by_id(
            id=dto.comment_id
        )

        if dto.user_id != comment.user_id:
            return UseCaseFailureOutput(
                type="not permission",
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        # body validator
        body_validate_result: dict = self._check_body_validator(body=dto.body)
        if not body_validate_result.get("status"):
            return UseCaseFailureOutput(
                type=body_validate_result.get("message"),
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        if self._has_banned_word(body=dto.body):
            return UseCaseFailureOutput(
                type="banned word",
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        self._community_repo.update_comment(dto=dto)

        return UseCaseSuccessOutput()


class DeleteCommentUseCase(CommunityBaseUseCase):
    def execute(
        self, dto: DeleteCommentDto
    ) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        comment: CommentEntity | None = self._community_repo.get_comment_by_id(
            id=dto.comment_id
        )

        if dto.user_id != comment.user_id:
            return UseCaseFailureOutput(
                type="not permission",
                message=FailureType.INVALID_REQUEST_ERROR,
                code=HTTPStatus.BAD_REQUEST,
            )

        self._community_repo.delete_comment(comment_id=dto.comment_id)

        return UseCaseSuccessOutput()


class UpdateCommentLikeStatusUseCase(CommunityBaseUseCase):
    def execute(
        self, dto: UpdateCommentLikeStatusDto
    ) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        if not self._community_repo.is_comment_like_status(dto=dto):
            self._community_repo.create_comment_like_status(dto=dto)
            self._community_repo.plus_like_count_for_comment(dto=dto)
        else:
            self._community_repo.update_comment_like_status(dto=dto)
            if dto.is_liked:
                self._community_repo.plus_like_count_for_comment(dto=dto)
            else:
                self._community_repo.minus_like_count_for_comment(dto=dto)

        return UseCaseSuccessOutput()


class GetFeedsUseCase(CommunityBaseUseCase):
    def execute(self, dto: GetFeedsDto) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        if self._user_repo.is_check_active_user(user_id=dto.user_id):
            return UseCaseFailureOutput(
                type="inactive user",
                message=FailureType.UNAUTHORIZED_ERROR,
                code=HTTPStatus.UNAUTHORIZED,
            )

        feed_entities: List[FeedEntity] | None = self._community_repo.get_feeds(
            post_category_id=dto.post_category_id,
            user_id=dto.user_id,
            previous_id=dto.previous_id,
        )

        if feed_entities:
            for feed_entity in feed_entities:
                created_at = feed_entity.created_at.strftime("%Y%m%d %H:%M:%S")
                past = datetime.datetime.strptime(
                    created_at, "%Y%m%d %H:%M:%S"
                ).replace(tzinfo=timezone("Asia/Seoul"))
                today = get_server_timestamp().replace(tzinfo=timezone("Asia/Seoul"))

                diff_time = today - past
                if diff_time.days >= 1:
                    make_diff_day = f"{diff_time.days}일전"

                    diff_month = trunc(diff_time.days / 30)
                    if diff_month > 6:
                        make_diff_day = f"오래전"
                    elif diff_month >= 1:
                        make_diff_day = f"{diff_month}달전"
                else:
                    diff_min = trunc(diff_time.seconds / 60)
                    make_diff_day = f"{diff_min}분전"

                    if diff_min >= 60:
                        diff_hour = trunc(diff_time.seconds / 3600)
                        make_diff_day = f"{diff_hour}시간전"

                feed_entity.create_diff_day = make_diff_day
        cursor = {
            "previous_id": feed_entities[-1].id if feed_entities else None,
            "current_id": dto.previous_id,
        }

        return UseCaseSuccessOutput(value={"data": feed_entities, "cursor": cursor})


class GetNoticesUseCase(CommunityBaseUseCase):
    def execute(self, dto: GetFeedsDto) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        if self._user_repo.is_check_active_user(user_id=dto.user_id):
            return UseCaseFailureOutput(
                type="inactive user",
                message=FailureType.UNAUTHORIZED_ERROR,
                code=HTTPStatus.UNAUTHORIZED,
            )

        notice_entities: List[NoticeEntity] | None = self._community_repo.get_notices(
            post_category_id=dto.post_category_id,
            user_id=dto.user_id,
            previous_id=dto.previous_id,
        )

        if notice_entities:
            for notice_entity in notice_entities:
                created_at = notice_entity.created_at.strftime("%Y%m%d %H:%M:%S")
                past = datetime.datetime.strptime(
                    created_at, "%Y%m%d %H:%M:%S"
                ).replace(tzinfo=timezone("Asia/Seoul"))
                today = get_server_timestamp().replace(tzinfo=timezone("Asia/Seoul"))

                diff_time = today - past
                if diff_time.days >= 1:
                    make_diff_day = f"{diff_time.days}일전"

                    diff_month = trunc(diff_time.days / 30)
                    if diff_month > 6:
                        make_diff_day = f"오래전"
                    elif diff_month >= 1:
                        make_diff_day = f"{diff_month}달전"
                else:
                    diff_min = trunc(diff_time.seconds / 60)
                    make_diff_day = f"{diff_min}분전"

                    if diff_min >= 60:
                        diff_hour = trunc(diff_time.seconds / 3600)
                        make_diff_day = f"{diff_hour}시간전"

                notice_entity.create_diff_day = make_diff_day
        cursor = {
            "previous_id": notice_entities[-1].id if notice_entities else None,
            "current_id": dto.previous_id,
        }

        return UseCaseSuccessOutput(value={"data": notice_entities, "cursor": cursor})


class GetPushFeedUseCase(CommunityBaseUseCase):
    def execute(
        self, dto: GetPushFeedDto
    ) -> UseCaseSuccessOutput | UseCaseFailureOutput:
        if self._user_repo.is_check_active_user(user_id=dto.user_id):
            return UseCaseFailureOutput(
                type="inactive user",
                message=FailureType.UNAUTHORIZED_ERROR,
                code=HTTPStatus.UNAUTHORIZED,
            )

        feed_entities: List[FeedEntity] | None = self._community_repo.get_push_feed(
            post_id=dto.post_id, user_id=dto.user_id,
        )

        if feed_entities:
            for feed_entity in feed_entities:
                created_at = feed_entity.created_at.strftime("%Y%m%d %H:%M:%S")
                past = datetime.datetime.strptime(
                    created_at, "%Y%m%d %H:%M:%S"
                ).replace(tzinfo=timezone("Asia/Seoul"))
                today = get_server_timestamp().replace(tzinfo=timezone("Asia/Seoul"))

                diff_time = today - past
                if diff_time.days >= 1:
                    make_diff_day = f"{diff_time.days}일전"

                    diff_month = trunc(diff_time.days / 30)
                    if diff_month > 6:
                        make_diff_day = f"오래전"
                    elif diff_month >= 1:
                        make_diff_day = f"{diff_month}달전"
                else:
                    diff_min = trunc(diff_time.seconds / 60)
                    make_diff_day = f"{diff_min}분전"

                    if diff_min >= 60:
                        diff_hour = trunc(diff_time.seconds / 3600)
                        make_diff_day = f"{diff_hour}시간전"

                feed_entity.create_diff_day = make_diff_day
        cursor = {
            "previous_id": feed_entities[-1].id if feed_entities else None,
            "current_id": dto.post_id,
        }

        return UseCaseSuccessOutput(value={"data": feed_entities, "cursor": cursor})
