from datetime import datetime
from typing import List

from dateutil.relativedelta import relativedelta
from sqlalchemy import exc, and_, func
from sqlalchemy.orm import contains_eager

from app.extensions.database import session
from app.extensions.utils.log_helper import logger_
from app.extensions.utils.math_helper import MathHelper
from app.extensions.utils.query_helper import RawQueryHelper
from app.extensions.utils.time_helper import get_server_timestamp
from app.persistence.model import PlantCategoryModel, PlantProfileModel, PlantInfoModel
from core.domains.plant.entity.plant_entity import (
    PlantCategoryEntity,
    PlantProfileEntity,
    WeeklyPlantEntity,
    MonthlyPlantEntity,
)
from core.domains.user.dto.user_dto import UpdateUserDto
from core.exceptions import NotUniqueErrorException

logger = logger_.getLogger(__name__)


class PlantRepository:
    def get_plant_categories(self) -> List[PlantCategoryEntity]:
        query_set = session.query(PlantCategoryModel).filter_by(is_available=True).all()
        return [result.to_entity() for result in query_set]

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

    def update_plant_profile(self, dto: UpdateUserDto) -> None:
        try:
            session.query(PlantProfileModel).filter_by(user_id=dto.user_id).update(
                {
                    "plant_category_id": dto.plant_category_id,
                    "name": dto.plant_nickname,
                    "start_growing_date": dto.start_growing_date,
                }
            )
            session.commit()
        except exc.IntegrityError as e:
            session.rollback()
            logger.error(
                f"[PlantRepository][update_plant_profile] user_id : {dto.user_id} error : {e}"
            )
            session.rollback()
            raise NotUniqueErrorException

    def get_plant_infos(self, user_id: int) -> PlantProfileEntity:
        query = (
            session.query(PlantProfileModel)
            .join(
                PlantInfoModel,
                and_(PlantProfileModel.id == PlantInfoModel.plant_id),
                isouter=True,
            )
            .options(contains_eager(PlantProfileModel.plant_infos))
            .filter(PlantProfileModel.user_id == user_id)
            .order_by(PlantInfoModel.id.desc())
            .limit(1)
        )
        query_set = query.first()
        return query_set.to_entity()

    def get_weekly_plant_infos(
        self, plant_id: int, div: str
    ) -> List[WeeklyPlantEntity | None]:
        field = eval(f"PlantInfoModel.{div}")
        query = (
            session.query(PlantInfoModel)
            .with_entities(PlantInfoModel.created_date, func.avg(field),)
            .filter(PlantInfoModel.plant_id == plant_id)
            .group_by(PlantInfoModel.created_date)
            .order_by(PlantInfoModel.created_date.desc())
            .limit(7)
        )
        query_set = query.all()

        return self._make_weekly_obj(query_set=query_set)

    def _make_weekly_obj(self, query_set: List) -> List[WeeklyPlantEntity | None]:
        date_dict = {0: "월", 1: "화", 2: "수", 3: "목", 4: "금", 5: "토", 6: "일"}
        result = list()
        current_day = get_server_timestamp().weekday()

        for data_ in query_set:
            day = datetime.strptime(data_[0], "%Y%m%d").weekday()
            current_day = day

            weekly_plant = WeeklyPlantEntity(
                day=date_dict.get(day), div_data=MathHelper.round(data_[1], 1)
            )
            result.append(weekly_plant)

        view_day = 7
        loop_num = view_day - len(query_set)
        for _ in range(loop_num):
            day = (
                MathHelper.get_before_day(current_day=current_day)
                if loop_num < 7
                else current_day
            )
            current_day = day

            weekly_plant = WeeklyPlantEntity(day=date_dict.get(day), div_data=None)
            result.append(weekly_plant)
            loop_num -= 1

        return result

    def get_monthly_plant_infos(
        self, plant_id: int, div: str
    ) -> List[WeeklyPlantEntity | None]:
        field = eval(f"PlantInfoModel.{div}")
        query = (
            session.query(PlantInfoModel)
            .with_entities(
                func.substring(PlantInfoModel.created_date, 1, 6), func.avg(field),
            )
            .filter(PlantInfoModel.plant_id == plant_id)
            .group_by(func.substring(PlantInfoModel.created_date, 1, 6))
            .order_by(func.substring(PlantInfoModel.created_date, 1, 6).desc())
            .limit(6)
        )
        query_set = query.all()
        return self._make_monthly_obj(query_set=query_set)

    def _make_monthly_obj(self, query_set: List) -> List[MonthlyPlantEntity | None]:
        result = list()
        current_month = get_server_timestamp().month

        for data_ in query_set:
            month = datetime.strptime(data_[0], "%Y%m").month
            current_month = month

            monthly_plant = MonthlyPlantEntity(
                month=str(month), div_data=MathHelper.round(data_[1], 1)
            )
            result.append(monthly_plant)

        view_month = 6
        loop_num = view_month - len(query_set)
        for _ in range(loop_num):
            month = (
                MathHelper.get_before_month(current_month=current_month)
                if loop_num < 6
                else current_month
            )
            current_month = month

            monthly_plant = MonthlyPlantEntity(month=str(month), div_data=None)
            result.append(monthly_plant)
            loop_num -= 1

        return result
