from math import ceil, floor
from .content_evaluator import ContentEvaluator


class DynamicRewardService:
    
    MAX_REACH_THRESHOLD = 7500 # if a user has a reach greater than this, they will be capped at this value for the purposes of calculating dynamic compensation

    MIN_CONTENT_SCORE = ContentEvaluator.MINIMUM_SCORE_THRESHOLD # minimum content score required to receive any compensation
    MAX_CONTENT_SCORE = 100 # maximum possible content score

    REACH_WEIGHT = .5 # weight of reach in the dynamic compensation calculation
    CONTENT_SCORE_WEIGHT = .5 # weight of content score in the dynamic compensation calculation

    # calculate the bounds of compensation given follower count, while content score is still unknown
    # for the minimum bound, we use the minimum content score, and for the maximum bound, we use the maximum content score
    # note: the lower bound will be rounded down and the upper bound will be rounded up
    # this is used for the range that will be sent to the user when they first receive an offer
    @staticmethod
    def calculate_tailored_compensation_bounds(min_compensation, max_compensation, reach, multiplier=1.0, ignore_reach=False):

        if multiplier > 1.0: # do not allow multipliers greater than 1.0
            raise ValueError("Multiplier cannot be greater than 1.0")
            
        tailored_min_compensation = DynamicRewardService.calculate_dynamic_compensation_amount(min_compensation, max_compensation, DynamicRewardService.MIN_CONTENT_SCORE, reach, multiplier=multiplier, ignore_reach=ignore_reach)
        tailored_max_compensation = DynamicRewardService.calculate_dynamic_compensation_amount(min_compensation, max_compensation, DynamicRewardService.MAX_CONTENT_SCORE, reach, multiplier=multiplier, ignore_reach=ignore_reach)
        return (floor(tailored_min_compensation), ceil(tailored_max_compensation))

    # calculate the compensation amount and round to the nearest 50 cents
    # this is used for the final number that is displayed to the user
    @staticmethod
    def calculate_rounded_dynamic_compensation_amount(min_compensation, max_compensation, content_score, reach, multiplier=None, ignore_reach=False):
        if multiplier is None:
            multiplier = 1.0
        compensation_amount = DynamicRewardService.calculate_dynamic_compensation_amount(min_compensation, max_compensation, content_score, reach, multiplier, ignore_reach)
        compensation_amount = round(compensation_amount * 2) / 2
        if compensation_amount.is_integer():
            return int(compensation_amount) # if the compensation amount rounds to a whole number, ensure there are no decimal places

        else:
            return round(compensation_amount, 2) # if the compensation amount rounds to a 50 cent increment, ensure there are two decimal places

    # based on the following formula:
    # Let compensation = c, r = normalized reach score, s = content score, min = min compensation, max = max compensation
    # Let range multiplier = m = ((s * 0.25) + (r * 0.75)) / 100 
    # c = (max - min)(m) + min
    @staticmethod
    def calculate_dynamic_compensation_amount(min_compensation, max_compensation, content_score, reach, multiplier=1.0, ignore_reach=False):
        """ Calculates the compensation amount based on the content score and reach. The compensation amount is a number between min_compensation and max_compensation.
                Args:
                    min_compensation (float): The minimum compensation amount.
                    max_compensation (float): The maximum compensation amount.
                    content_score (float): The content score of the user.
                    reach (int): The reach of the user.
                    multiplier (float): The multiplier to apply to the compensation amount.
                    ignore_reach (bool): Whether or not to ignore the reach in the calculation. Used for UGC content where reach is not applicable.
                Returns:
                    float: The compensation amount.
        """

        normalized_content_score = DynamicRewardService.normalize_content_score(content_score)
        normalized_reach = DynamicRewardService.normalize_reach(reach)

        # calculate the compensation amount based on the content score and reach. The compensation amount is a number between min_compensation and max_compensation.
        combined_range_factor = DynamicRewardService.calculate_combined_range_factor(normalized_content_score, normalized_reach) if not ignore_reach else DynamicRewardService.calculate_combined_range_factor(normalized_content_score, normalized_reach, reach_weight=0, content_score_weight=1)
        compensation_amount = ((max_compensation - min_compensation) * combined_range_factor) + min_compensation

        # factor in the multiplier to the compensation amount (but don't exceed max_compensation)
        compensation_amount *= multiplier

        return max(min_compensation, min(compensation_amount, max_compensation))
    

    # Based on the following formula:
    # Let range multiplier = m = ((s * 0.25) + (r * 0.75)) / 100
    @staticmethod   
    def calculate_combined_range_factor(content_score, normalized_reach, reach_weight=REACH_WEIGHT, content_score_weight=CONTENT_SCORE_WEIGHT):
        return ((normalized_reach * reach_weight) + (content_score * content_score_weight)) / 100

    # normalize the reach to be between 0 and 100, using the max_reach_threshold as the upper bound
    @staticmethod
    def normalize_reach(reach):
        return min((reach / DynamicRewardService.MAX_REACH_THRESHOLD) * 100, 100)
    
    # normalize the content score to be between 0 and 100, considering that there is a minimum passing content score
    @staticmethod
    def normalize_content_score(content_score):
        return max(((content_score - DynamicRewardService.MIN_CONTENT_SCORE) * 100) / (DynamicRewardService.MAX_CONTENT_SCORE - DynamicRewardService.MIN_CONTENT_SCORE), 0)
