from typing import List

from sqlalchemy import exc, and_, func
from sqlalchemy.orm import joinedload, contains_eager, selectinload

from app.extensions.database import session
from app.extensions.utils.log_helper import logger_
from app.extensions.utils.query_helper import RawQueryHelper
from app.extensions.utils.time_helper import get_server_timestamp
from app.persistence.model import (
    PostModel,
    PostAttachmentModel,
    UserModel,
    PostCategoryModel,
    PostLikeStatusModel,
    CommentModel,
    CommentLikeStatusModel,
    CustomerInquiryModel,
    InquiryAttachmentModel,
)
from core.domains.admin.dto.admin_dto import (
    CreateAdminPostDto,
    CreateAdminPostAttachmentDto,
    UpdateAdminPostDto,
)
from core.domains.community.entity.community_entity import (
    PostEntity,
    PostCategoryEntity,
    FeedEntity,
)
from core.domains.community.enum.community_enum import (
    LastUserActionEnum,
    LastAdminActionEnum,
    PostCategoryEnum,
)
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 AdminRepository:
    def commit_transaction(self) -> None:
        try:
            session.commit()
        except exc.IntegrityError as e:
            logger.error(f"[AdminRepository][commit_transaction] error : {e}")
            session.rollback()
            raise NotUniqueErrorException

    def create_post(self, dto: CreateAdminPostDto) -> int | None:
        try:
            model = PostModel(
                user_id=dto.user_id,
                post_category_id=dto.post_category_id,
                title=dto.title,
                body=dto.body,
                last_user_action=LastUserActionEnum.CREATE.value,
                last_user_action_at=get_server_timestamp(),
                last_admin_action=LastAdminActionEnum.CREATE.value,
                last_admin_action_at=get_server_timestamp(),
            )
            session.add(model)
            session.flush()

            return model.id
        except exc.IntegrityError as e:
            logger.error(
                f"[AdminRepository][create_post] user_id : {dto.user_id}, error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def create_post_attachment(self, dto: CreateAdminPostAttachmentDto) -> None:
        try:
            model = PostAttachmentModel(
                post_id=dto.post_id,
                uuid=dto.uuid,
                file_name=dto.file_name,
                path=dto.path,
                extension=dto.extension,
            )
            session.add(model)
            session.flush()
        except exc.IntegrityError as e:
            logger.error(
                f"[AdminRepository][create_post_attachment] post_id : {dto.post_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def is_check_admin_user(self, user_id: int) -> bool:
        query = session.query(
            UserModel.query.filter(
                UserModel.id == user_id, UserModel.group == UserGroupEnum.ADMIN.value
            ).exists()
        )
        query_set = query.scalar()
        return query_set

    def get_post_by_id(self, id: int) -> PostEntity | None:
        query = (
            session.query(PostModel)
            .options(joinedload(PostModel.post_category, innerjoin=True))
            .options(joinedload(PostModel.comments))
            .options(joinedload(PostModel.post_like_statuses))
            .join(
                PostAttachmentModel,
                PostAttachmentModel.post_id == PostModel.id,
                isouter=True,
            )
            .filter(PostModel.id == id)
            .order_by(PostAttachmentModel.id)
        )
        query_set = query.first()

        if not query_set:
            return None

        return query_set.to_entity()

    def update_post(self, dto: UpdateAdminPostDto) -> None:
        try:
            session.query(PostModel).filter_by(id=dto.post_id).update(
                {
                    "post_category_id": dto.post_category_id,
                    "title": dto.title,
                    "body": dto.body,
                    "last_admin_action": LastAdminActionEnum.UPDATE.value,
                    "last_admin_action_at": get_server_timestamp(),
                }
            )
            session.flush()
        except exc.IntegrityError as e:
            session.rollback()
            logger.error(
                f"[AdminRepository][update_post] post_id : {dto.post_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def delete_post_attachments(self, delete_files: List[int]) -> None:
        try:
            session.query(PostAttachmentModel).filter(
                PostAttachmentModel.id.in_(delete_files)
            ).update(
                {"is_available": False,}
            )
            session.flush()
        except exc.IntegrityError as e:
            session.rollback()
            logger.error(
                f"[AdminRepository][delete_post_attachments] delete_files : {delete_files} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def delete_post(self, post_id: int) -> None:
        try:
            session.query(PostModel).filter_by(id=post_id).update(
                {
                    "is_deleted": True,
                    "last_admin_action": LastAdminActionEnum.DELETE.value,
                    "last_admin_action_at": get_server_timestamp(),
                }
            )
            session.commit()
        except exc.IntegrityError as e:
            session.rollback()
            logger.error(
                f"[AdminRepository][delete_post] post_id : {post_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def get_post_categories(self) -> List[PostCategoryEntity]:
        post_categories = [
            PostCategoryEnum.ALL.value,
        ]

        query_set = (
            session.query(PostCategoryModel)
            .filter_by(is_available=True,)
            .filter(~PostCategoryModel.id.in_(post_categories))
            .order_by(PostCategoryModel.id)
            .all()
        )
        return [result.to_entity() for result in query_set]

    def get_total_user(self, target_user_id: int, nickname: str) -> int:
        filters = list()
        if target_user_id:
            filters.append(UserModel.id == target_user_id)

        if nickname:
            search_word = f"%{nickname}%"
            filters.append(UserModel.nickname.like(search_word))

        query = session.query(func.count(UserModel.id).filter(*filters))
        query_set = query.first()
        return int(query_set[0])

    def get_total_post(self, post_category_id: int, target_user_id: int) -> int:
        filters = list()
        filters.append(PostModel.is_deleted == False)

        if post_category_id != PostCategoryEnum.ALL.value:
            filters.append(PostModel.post_category_id == post_category_id)

        if target_user_id:
            filters.append(PostModel.user_id == target_user_id)

        query = session.query(func.count(PostModel.id).filter(*filters))
        query_set = query.first()
        return int(query_set[0])

    def get_feeds(
        self,
        post_category_id: int,
        user_id: int,
        page_no: int | None,
        target_user_id: int | None,
    ) -> List[FeedEntity] | None:
        page_no = 1 if not page_no else page_no
        page_size = 10

        filters = list()
        filters.append(PostModel.is_deleted == False)

        if post_category_id != PostCategoryEnum.ALL.value:
            filters.append(PostModel.post_category_id == post_category_id)

        if target_user_id:
            filters.append(PostModel.user_id == target_user_id)

        query = (
            session.query(PostModel)
            .options(joinedload(PostModel.post_category, innerjoin=True))
            .options(selectinload(PostModel.post_attachments))
            .options(joinedload(PostModel.user, innerjoin=True))
            .options(joinedload("user.user_profile"))
            .options(joinedload("user.plant_profile"))
            .options(selectinload(PostModel.comments))
            .options(joinedload("comments.user.plant_profile", innerjoin=True))
            .filter(*filters)
            .order_by(PostModel.id.desc())
            .limit(page_size)
            .offset((page_no - 1) * page_size)
        )

        query_set = query.all()

        if not query_set:
            return None

        return [query.to_admin_feed_entity() for query in query_set]

    def delete_comment(self, comment_id: int) -> None:
        try:
            session.query(CommentModel).filter_by(id=comment_id).update(
                {
                    "is_deleted": True,
                    "last_admin_action": LastAdminActionEnum.DELETE.value,
                    "last_admin_action_at": get_server_timestamp(),
                }
            )
            session.commit()
        except exc.IntegrityError as e:
            session.rollback()
            logger.error(
                f"[CommunityRepository][delete_comment] comment_id : {comment_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def get_user(
        self, target_user_id: int, nickname: str, page_no: int | None
    ) -> List[UserEntity] | None:
        page_no = 1 if not page_no else page_no
        page_size = 10

        filters = list()

        if target_user_id:
            filters.append(UserModel.id == target_user_id)

        if nickname:
            search_word = f"%{nickname}%"
            filters.append(UserModel.nickname.like(search_word))

        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.plant_profile, innerjoin=True))
            .options(joinedload(UserModel.user_profile))
            .filter(*filters)
            .order_by(UserModel.id.desc())
            .limit(page_size)
            .offset((page_no - 1) * page_size)
        )
        query_set = query.all()

        if not query_set:
            return None

        return [query.to_entity() for query in query_set]

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

    def get_total_cs(self, is_solved: bool, email: str) -> int:
        filters = list()
        filters.append(CustomerInquiryModel.is_solved == is_solved)

        if email:
            search_word = f"%{email}%"
            filters.append(CustomerInquiryModel.email.like(search_word))

        query = session.query(func.count(CustomerInquiryModel.id).filter(*filters))
        query_set = query.first()
        return int(query_set[0])

    def get_cs(
        self, is_solved: bool, email: str | None, page_no: int | None
    ) -> List[FeedEntity] | None:
        page_no = 1 if not page_no else page_no
        page_size = 10

        filters = list()
        filters.append(CustomerInquiryModel.is_solved == is_solved)

        if email:
            search_word = f"%{email}%"
            filters.append(CustomerInquiryModel.email.like(search_word))

        query = (
            session.query(CustomerInquiryModel)
            .options(joinedload(CustomerInquiryModel.inquiry_attachments))
            .filter(*filters)
            .order_by(CustomerInquiryModel.id.desc())
            .limit(page_size)
            .offset((page_no - 1) * page_size)
        )
        query_set = query.all()

        if not query_set:
            return None

        return [query.to_entity() for query in query_set]

    def update_cs(self, inquiry_id: int, is_solved: bool) -> None:
        try:
            session.query(CustomerInquiryModel).filter_by(id=inquiry_id).update(
                {"is_solved": is_solved,}
            )
            session.commit()
        except exc.IntegrityError as e:
            session.rollback()
            logger.error(
                f"[AdminRepository][update_cs] inquiry_id : {inquiry_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException
