"""
User preference evaluator - evaluate whether the itinerary meets user preferences
"""
import math
from typing import Dict, Any, Tuple, List
from .base_evaluator import BaseEvaluator
from utils.poi_analyzer import POIAnalyzer
from utils.preference_enum import POI_TYPE_CN, POI_TYPE_EN, HIGH_EFFERT_score
from collections import defaultdict
import json



class PreferenceEvaluator(BaseEvaluator):
    """Preference Evaluator Class"""

    def __init__(self, preference_weight: float = 1.0, poi_analyzer: POIAnalyzer=None):
        """
        Initialize Preference Evaluator
        
        Args:
            preference_weight: Preference Weight
        """
        super().__init__("PreferenceEvaluator")
        self.preference_weight = preference_weight
        if poi_analyzer is not None:
            self.poi_data = poi_analyzer
        else:
            self.poi_data = POIAnalyzer()
        # Check poi information   self.poi_data.read_poi_api_info
        # Check hotel information  self.poi_data.get_hotel_info

    def evaluate(self, data: Dict[str, Any]) -> Tuple[float, Dict[str, Any]]:
        """
        Evaluate User Preference Matching

        Args:
            data: Dictionary containing the following keys:
                - all_content: Content Data
                - itinerary: Itinerary Data
                - poi_dict: POI Dictionary
                - user_preferences: User Preference Data
                
        Returns:
            Tuple[float, Dict]: (Preference Matching Score, Evaluation Details)
        """
        itinerary = data.get("itinerary", {})
        poi_dict = data.get("poi_dict", {})
        user_preferences = poi_dict.get("preference", {})

        # Here you can implement specific preference evaluation logic
        # For example: Check Attraction Type Preference, Time Arrangement Preference, Budget Preference, etc.
        if not isinstance(user_preferences, dict):
            user_preferences = json.loads(user_preferences)

        preference_score, details = self._evaluate_preferences(itinerary, poi_dict, user_preferences)

        details = {
            "preference_score": preference_score,
            "user_preferences": user_preferences,
            "evaluation_method": "preference_matching",
            "score_details": details
        }

        return preference_score, details

    def _evaluate_preferences(self, itinerary: Dict, poi_dict: Dict,
                              user_preferences: Dict) -> float:
        """
        Evaluate Preference Matching
        
        Args:
            itinerary: Itinerary Data
            poi_dict: POI Dictionary
            user_preferences: User Preference
            
        Returns:
            Preference Matching Score
        """
        score = 0.0
        weights_sum = 0.0
        
        # Initialize score variables to avoid "variable not associated with a value" error
        budget_score = -1
        time_score = -1
        attraction_score = -1
        effort_score = -1
        self.SCORE_WEIGHTS = {
            "attraction": 0.2,
            "pacing": 0.6,
            "budget": 0.6,
            "physical": 0.6
        }

        # Check Budget Preference
        if poi_dict['locale'] == 'zh-CN':
            preference_keys = ['价格偏好', '行程偏好', '景点偏好', '体力偏好']
        else:
            preference_keys = ['Price preference', 'Itinerary Preference', 'Scenic Spot Preference',
                               'Effort Preference']

        budget_preference = user_preferences.get(preference_keys[0], "")
        if len(budget_preference) != 0:
            budget_score = self._check_budget_compliance(itinerary, budget_level=budget_preference, poi_dict=poi_dict)
            score += budget_score * self.SCORE_WEIGHTS["budget"]
            weights_sum += self.SCORE_WEIGHTS["budget"]

        # Check Time Preference
        time_preference = user_preferences.get(preference_keys[1], "Moderate")
        if len(time_preference) != 0:
            time_score = self._check_time_preference(itinerary, time_preference, poi_dict)
            score += time_score * self.SCORE_WEIGHTS["pacing"]
            weights_sum += self.SCORE_WEIGHTS["pacing"]

        # Check Attraction Type Preference
        attraction_preferences = user_preferences.get(preference_keys[2], [])
        if len(attraction_preferences) != 0:
            attraction_score = self._check_attraction_preferences(itinerary, attraction_preferences, poi_dict)
            score += attraction_score * self.SCORE_WEIGHTS["attraction"]
            weights_sum += self.SCORE_WEIGHTS["attraction"]

        physical_effort_preference = user_preferences.get(preference_keys[3], 'Moderate')
        if len(physical_effort_preference) != 0:
            effort_score = self._check_physical_effort_preference(itinerary, physical_effort_preference, poi_dict)
            score += effort_score * self.SCORE_WEIGHTS["physical"]
            weights_sum += self.SCORE_WEIGHTS["physical"]

        # Calculate Final Score (Normalized to 0-1 Range)
        final_score = score / weights_sum if weights_sum > 0 else 1.0

        return final_score * self.preference_weight, {"effort_score": effort_score, "attraction_score": attraction_score, "time_score": time_score, "budget_score": budget_score, "weights_sum": weights_sum}

    def _check_budget_compliance(self, itinerary: Dict, budget_level: str, poi_dict: Dict) -> float:
        """
        budget_level:
        High-end
        Save money
        Comfortable
        
        Args:
            itinerary: Itinerary Data
            budget_level: Budget Level

        Returns:
            Budget Compliance Score
        """
        score = 0.0
        total_items = 0

        # Check Hotel Budget
        for day_info in itinerary.get("dayInfos", []):
            for schedule in day_info.get("scheduleDetail", []):
                for item in schedule.get("detailList", []):
                    if item.get("type") == "hotel":
                        hotel_id = item.get("id")
                        # if hotel_id in hotel_info:
                        hotel_data = self.poi_data.get_hotel_info(hotel_id, poi_dict['locale'])
                        try:
                            hotel_price_level = hotel_data['star']  # Star
                            print(f"Hotel ID: {hotel_id}, Price Level: {hotel_price_level}")
                            if budget_level in ['省钱', 'Save money', '性价比高', 'Cost-effective'] and hotel_price_level in [
                                "0", "1", "2"]:
                                score += 1.0
                            elif budget_level in ['Comfortable', '舒适'] and hotel_price_level in ['3', "4"]:
                                score += 1.0
                            elif budget_level in ['高端', 'High-end'] and hotel_price_level in ['5']:
                                score += 1.0
                            total_items += 1
                        except Exception as e:
                            print(f"Error processing hotel ID {hotel_id}: {e}")
                            continue

        # Check Transport Budget
        # # transport_info = all_content.get("transport_info", {})
        # for day_info in itinerary.get("dayInfos", []):
        #     for schedule in day_info.get("scheduleDetail", []):
        #         for item in schedule.get("detailList", []):
        #             if item.get("type") == "transportation":
        #                 transport_id = item.get("id")
        #                 if transport_id in transport_info:
        #                     transport_data = transport_info[transport_id]
        #                     transport_price = transport_data.get("price", 0)
        #
        #                     if budget_level == "low" and transport_price <= 30:
        #                         score += 1.0
        #                     elif budget_level == "medium" and transport_price <= 100:
        #                         score += 1.0
        #                     elif budget_level == "high":
        #                         score += 1.0
        #                     total_items += 1
        #
        return score / total_items if total_items > 0 else 0.0

    def _check_time_preference(self, itinerary: Dict, time_preference: str, poi_dict: Dict) -> float:
        """
        Args:
            itinerary: Itinerary Data
            time_preference: Time Preference
            
        Returns:
            Time Preference Matching Score
        """

        def get_loose_score(num_activities: int, min_total_hours: float, max_total_hours: float,
                            num_transportations: int) -> float:
            threshold = (num_activities <= 4 and
                         min_total_hours >= 2 and
                         max_total_hours <= 13 and
                         num_transportations <= 3)
            return (1.0 if threshold else
                    get_moderate_score(num_activities, min_total_hours, max_total_hours, num_transportations) * 0.8)

        def get_moderate_score(num_activities: int, min_total_hours: float, max_total_hours: float,
                               num_transportations: int) -> float:
            threshold = (2 <= num_activities <= 6 and
                         3 <= min_hours_total and
                         max_total_hours <= 15 and
                         num_transportations <= 5)
            return 1.0 if threshold else 0.5

        def get_tight_score(num_activities: int, min_total_hours: float, max_total_hours: float,
                            num_transportations: int) -> float:
            threshold = (num_activities >= 3 and
                         min_total_hours >= 4 and
                         max_total_hours <= 17)
            return (1.0 if threshold else
                    get_moderate_score(num_activities, min_total_hours, max_total_hours, num_transportations) * 0.8)

        TIME_PREFERENCE_MAP = {
            "宽松": get_loose_score,
            "Loose": get_loose_score,
            "适中": get_moderate_score,
            "Moderate": get_moderate_score,
            "紧凑": get_tight_score,
            "Tight": get_tight_score
        }

        try:
            get_score = TIME_PREFERENCE_MAP[time_preference]
        except KeyError:
            raise ValueError(f"Invalid time preference: {time_preference}")

        def get_activities(day: Dict) -> List[Dict]:
            return [
                detail
                for schedule in day["scheduleDetail"]
                for detail in schedule["detailList"]
                if detail["type"] == "poi"
            ]

        def get_transportations(day: Dict) -> List[Dict]:
            return [
                detail
                for schedule in day["scheduleDetail"]
                for detail in schedule["detailList"]
                if detail["type"] == "transportation"
            ]

        days = itinerary.get("dayInfos", [])
        num_days = len(days)
        score = 0
        for day in days:
            activities = get_activities(day)
            transportations = get_transportations(day)
            num_activities = len(activities)
            num_transportations = len(transportations)

            min_hours_total, max_hours_total = 0, 0
            for activity in activities:
                activity_id = activity.get("id", "")
                if activity_id != "":
                    api_info = self.poi_data.read_poi_api_info(activity_id, poi_dict['locale'])
                    poi_info_simple = api_info[0]
                    poi_info = api_info[2]
                    try:
                        min_hours, max_hours = self.poi_data.calculate_poi_hours(poi_info_simple)
                    except Exception as e:
                        print("error in calculate_poi_hours", str(e))
                        min_hours, max_hours = 0, 0
                    min_hours_total += min_hours
                    max_hours_total += max_hours

            score += get_score(num_activities, min_hours_total, max_hours_total, num_transportations)

        return score / num_days if num_days > 0 else 0.0

    def _check_attraction_preferences(self, itinerary: Dict, attraction_preferences: List[str],
                                      poi_dict: Dict) -> float:
        """
        Args:
            itinerary: Itinerary Data
            attraction_preferences: Attraction Type Preference List
            poi_dict: POI Dictionary
            
        Returns:
            Attraction Type Preference Matching Score
        """
        matched_attractions = 0
        total_attractions = 0

        for day_info in itinerary.get("dayInfos", []):
            print("day：", day_info["day"])
            for schedule in day_info.get("scheduleDetail", []):
                for item in schedule.get("detailList", []):
                    if item.get("type") == "poi":
                        poi_id = item.get("id")
                        
                        try:
                            poi_data, _, _ = self.poi_data.read_poi_api_info(poi_id, poi_dict['locale'])
                            poi_type = poi_data.get("tag", "")
                            print(f"POI ID: {poi_id}, Type: {poi_type}")

                            
                            if poi_dict['locale'] == "zh_CN":
                                preference_mapping = POI_TYPE_CN
                            else:
                                preference_mapping = POI_TYPE_EN

                            prefer_mapping_all = []  # When there are multiple preferences, map the preferences to gather, prevent the repetition of calculation when a scenic spot satisfies two or more preferences at the same time
                            for preference in attraction_preferences:
                                if preference in preference_mapping:
                                    prefer_mapping_all.extend(preference_mapping[preference])
                            for tag in poi_type:
                                if tag in prefer_mapping_all:
                                    matched_attractions += 1
                                    break

                            total_attractions += 1
                        except Exception as e:
                            print(f"Error reading POI data for ID: {poi_id}, Error: {e}")
                            continue

        return matched_attractions / total_attractions if total_attractions > 0 else 0.0

    def _check_physical_effort_preference(self, itinerary: Dict, effort_preference: str, poi_dict: Dict) -> float:
        """
        Check Physical Effort Preference Matching

        Args:
            itinerary: Itinerary Data
            effort_preference: Physical Effort Preference ("light", "moderate", "strenuous")
            poi_dict : POI Dictionary

        Returns:
            Physical Effort Preference Matching Score
        """
        if not effort_preference:
            return 0.0

        total_activities = 0
        high_activity = 0
        play_hours = 0
        high_activity_play_hours = 0
        today = {}
        pre_day = {}
        absolute_high_effort_flag = 0
        absolute_low_effort_flag = 0
        for day_info in itinerary.get("dayInfos", []):
            today = self._get_day_high_effort_result(day_info, poi_dict['locale'])
            if pre_day != {}:
                if today['high_activity_score'] >= 4 or (
                        today['high_activity_score'] != 0 and pre_day['high_activity_score'] != 0):
                    absolute_high_effort_flag = 1  # Absolute High Physical Effort

                if today['high_activity'] == 1 and pre_day['high_activity'] == 1 \
                        and today['day_min_play_hours'] <= 4 and pre_day['day_min_play_hours'] <= 4:  # Absolute Low Physical Effort
                    absolute_low_effort_flag = 1

            pre_day = today.copy()
            total_activities += today['total_activities']
            high_activity += today['high_activity']
            play_hours += today['day_min_play_hours']
            high_activity_play_hours += today['high_activity_play_hours']

        if total_activities == 0:
            return 0.0

        effort_ratio = high_activity / total_activities  # High Physical Effort Activity Ratio
        play_hours_ratio = play_hours / len(itinerary.get("dayInfos", [])) * 8  # Play Hours Ratio
        high_activity_hours_ratio = high_activity_play_hours / (play_hours if play_hours else 1)  # High Physical Effort Activity Market Ratio
        print(f"high_activity: {high_activity},total_activities: {total_activities} ")

        threshold_mapping = {
            "Light": (effort_ratio < 0.1),
            '轻松': (effort_ratio < 0.1),
            'Moderate': (0.1 <= effort_ratio <= 0.25
                         or (effort_ratio < 0.1 and 0.2 <= play_hours_ratio <= 0.5 and high_activity_hours_ratio >= 0.4)
                         or (play_hours_ratio < 0.2 and high_activity_hours_ratio >= 0.3)),
            "适中": (0.1 <= effort_ratio <= 0.25
                   or (effort_ratio < 0.1 and 0.2 <= play_hours_ratio <= 0.5 and high_activity_hours_ratio >= 0.4)),
            "Strenuous": (effort_ratio >= 0.25
                          or (0.1 <= effort_ratio < 0.25 and play_hours_ratio >= 0.5 and high_activity_hours_ratio >= 0.3)),
            '费力': (effort_ratio >= 0.25
                   or (0.1 <= effort_ratio < 0.25 and play_hours_ratio >= 0.5 and high_activity_hours_ratio >= 0.3)
                   ),
        }

        # Calculate Score Based on Preference
        threshold = threshold_mapping[effort_preference]
        if effort_preference in ["Light", '轻松']:
            if absolute_high_effort_flag == 1:
                return 0.0
            elif threshold:
                return 1.0
            elif threshold_mapping['Moderate']:
                return 0.5
            else:
                return 0

        elif effort_preference in ["Moderate", '适中']:
            if threshold or (absolute_high_effort_flag * absolute_low_effort_flag > 0):
                return 1.0
            else:
                return 0.5

        elif effort_preference in ["Strenuous", '费力']:
            if absolute_low_effort_flag == 1:
                return 0.0
            elif threshold:
                return 1.0
            elif threshold_mapping['Moderate']:
                return 0.5
            else:
                return 0

        return 0.0

    def _get_high_effort_result(self, activity_name: str, poi_id: str, locale: str) -> tuple[int | Any, int, float]:
        """
        Get High Physical Effort Activity Result
        Args:
            activity_name: Activity Name
            poi_id: POI ID
            locale: Language Environment
        Returns:
            (effort_cost, high_activity, min_hours)

        """
        poi_data, min_spend_time, poi_info = self.poi_data.read_poi_api_info(int(poi_id), locale)
        en_name = poi_data['ename'].lower()
        poi_tag = poi_data['tag']
        effort_cost = 0
        min_hours, max_hours = self.poi_data.calculate_poi_hours(poi_data)

        # Match the effert_cost in HIGH_EFFERT_score based on the name and tag
        for key, activities in HIGH_EFFERT_score.items():
            for activity in activities:
                if activity in poi_tag or activity.lower() in en_name:
                    temp = int(key)
                    # if temp == 1 and min_hours <= 8:
                    #     temp = 0
                    effort_cost = max(effort_cost, temp)
        return effort_cost, 1 if effort_cost != 0 else 0, min_hours

    def _get_day_high_effort_result(self, dayinfo: Dict, locale: str) -> Dict:
        """
        Get Daily High Physical Effort Activity Result
        Args:
            dayinfo: Daily Itinerary Information
            locale: Language Environment
        Returns:
            Dict: Contains the result of the high physical effort activity
        """
        # Calculate the total number of scenic spots, the score and number of high physical effort scenic spots, and the shortest play hours
        # today
        day_records = {'total_activities': 0,
                       'high_activity': 0,
                       'high_activity_score': 0,
                       'day_min_play_hours': 0,
                       "high_activity_play_hours": 0}

        for schedule in dayinfo.get("scheduleDetail", []):
            for item in schedule.get("detailList", []):
                if item.get("type") == "poi":
                    day_records['total_activities'] += 1
                    poi_id = item.get("id")
                    poi_name = item.get("name", "")
                    try:
                        score, num, min_play_hours = self._get_high_effort_result(poi_name, poi_id, locale)
                        day_records['high_activity_score'] += score
                        day_records['high_activity'] += num
                        day_records['day_min_play_hours'] += min_play_hours
                        if num == 1:
                            day_records['high_activity_play_hours'] += min_play_hours
                    except:
                        continue

        return day_records
