import time
import hashlib

class DynamicConfidenceScorer:
    """
    A class for calculating dynamic confidence scores based on context, time, and feedback.
    Implements real-time confidence score calculation, context-adaptive scoring,
    confidence decay over time, and calibration from feedback.  Mimics the
    functionality outlined in Patent 6: Dynamic Confidence Scoring.
    """

    def __init__(self, initial_confidence=0.5, decay_rate=0.01, learning_rate=0.1, context_weights=None, domain="default"):
        """
        Initializes the DynamicConfidenceScorer.

        Args:
            initial_confidence (float): The initial confidence score.  Defaults to 0.5.
            decay_rate (float): The rate at which the confidence decays per second. Defaults to 0.01.
            learning_rate (float): The rate at which the confidence score is updated based on feedback. Defaults to 0.1.
            context_weights (dict): A dictionary of weights for different context features.  Defaults to None.
            domain (str): A string representing the domain of this confidence scorer.  Used for contextual adaptation. Defaults to "default".
        """
        self.confidence = initial_confidence
        self.last_updated = time.time()
        self.decay_rate = decay_rate
        self.learning_rate = learning_rate
        self.context_weights = context_weights if context_weights is not None else {}  # Initialize with empty dict if None
        self.domain = domain
        self.domain_specific_parameters = {} # Store domain specific parameters
        self.item_history = {} # Store history of item performance for contextual adaptation


    def calculate_decay(self):
        """
        Calculates the confidence decay based on the time elapsed since the last update.
        """
        time_elapsed = time.time() - self.last_updated
        decay = self.decay_rate * time_elapsed
        return decay


    def get_confidence(self, context=None, item_id=None):
        """
        Returns the current confidence score, taking into account decay and context.

        Args:
            context (dict, optional): A dictionary of context features. Defaults to None.
            item_id (str, optional): Unique identifier for the item being scored. Defaults to None.

        Returns:
            float: The current confidence score.
        """
        decay = self.calculate_decay()
        self.confidence = max(0, self.confidence - decay)  # Ensure confidence doesn't go below 0

        if context:
            self.confidence = self.adapt_to_context(self.confidence, context, item_id)

        return self.confidence


    def adapt_to_context(self, confidence, context, item_id=None):
        """
        Adapts the confidence score based on the provided context.

        Args:
            confidence (float): The current confidence score.
            context (dict): A dictionary of context features.
            item_id (str, optional): Unique identifier for the item being scored. Defaults to None.

        Returns:
            float: The adjusted confidence score.
        """

        adjusted_confidence = confidence

        # Domain-specific adaptation
        if self.domain in self.domain_specific_parameters:
            domain_params = self.domain_specific_parameters[self.domain]
            # Example: Adjust confidence based on domain-specific thresholds
            if confidence < domain_params.get("low_confidence_threshold", 0.2):
                adjusted_confidence *= domain_params.get("low_confidence_multiplier", 0.8)
            elif confidence > domain_params.get("high_confidence_threshold", 0.8):
                adjusted_confidence *= domain_params.get("high_confidence_multiplier", 1.2)

        # Feature-based adaptation
        for feature, value in context.items():
            if feature in self.context_weights:
                weight = self.context_weights[feature]
                adjusted_confidence += weight * value  # Simple weighted sum

        # Item-specific adaptation (using item_id)
        if item_id:
            if item_id in self.item_history:
                history = self.item_history[item_id]
                success_rate = history["successes"] / (history["successes"] + history["failures"] + 1e-9) # Avoid division by zero
                adjusted_confidence += (success_rate - 0.5) * 0.1 # Adjust based on success rate
            else:
                # Initialize item history
                self.item_history[item_id] = {"successes": 0, "failures": 0}


        return max(0, min(1, adjusted_confidence))  # Ensure confidence remains within [0, 1]


    def update_confidence(self, validation_outcome, item_id=None):
        """
        Updates the confidence score based on validation outcome (True for correct, False for incorrect).

        Args:
            validation_outcome (bool): True if the outcome was correct, False otherwise.
            item_id (str, optional): Unique identifier for the item being scored. Defaults to None.
        """
        if validation_outcome:
            self.confidence += self.learning_rate * (1 - self.confidence)
        else:
            self.confidence -= self.learning_rate * self.confidence

        self.confidence = max(0, min(1, self.confidence))  # Ensure confidence remains within [0, 1]
        self.last_updated = time.time()

        # Update item history
        if item_id and item_id in self.item_history:
            if validation_outcome:
                self.item_history[item_id]["successes"] += 1
            else:
                self.item_history[item_id]["failures"] += 1


    def set_context_weights(self, context_weights):
        """
        Sets the context weights.

        Args:
            context_weights (dict): A dictionary of weights for different context features.
        """
        self.context_weights = context_weights


    def set_domain_parameters(self, domain, parameters):
        """
        Sets domain-specific parameters.

        Args:
            domain (str): The domain for which to set parameters.
            parameters (dict): A dictionary of domain-specific parameters.
        """
        self.domain_specific_parameters[domain] = parameters

    def reset_confidence(self, initial_confidence):
        """
        Resets the confidence to a new initial value.

        Args:
            initial_confidence (float): The new initial confidence score.
        """
        self.confidence = initial_confidence
        self.last_updated = time.time()


def generate_item_id(item_data):
    """
    Generates a unique item ID from item data using SHA-256.

    Args:
        item_data (str or bytes): The data representing the item.  Should be a string or bytes.

    Returns:
        str: A unique item ID (SHA-256 hash).
    """
    if isinstance(item_data, str):
        item_data = item_data.encode('utf-8')  # Encode string to bytes if necessary
    return hashlib.sha256(item_data).hexdigest()


if __name__ == '__main__':
    # Example Usage
    scorer = DynamicConfidenceScorer(initial_confidence=0.7, decay_rate=0.005, learning_rate=0.2, domain="e-commerce")

    # Set context weights
    scorer.set_context_weights({"user_age": 0.01, "item_popularity": 0.05})

    # Set domain-specific parameters
    scorer.set_domain_parameters("e-commerce", {"low_confidence_threshold": 0.3, "low_confidence_multiplier": 0.7,
                                                "high_confidence_threshold": 0.9, "high_confidence_multiplier": 1.1})

    # Simulate usage with different contexts and feedback
    item_data = "ProductX: Price $25, Category Electronics"
    item_id = generate_item_id(item_data)

    context1 = {"user_age": 30, "item_popularity": 0.8}
    confidence1 = scorer.get_confidence(context=context1, item_id=item_id)
    print(f"Initial confidence: {confidence1}")

    time.sleep(5)  # Simulate some time passing

    confidence2 = scorer.get_confidence(context=context1, item_id=item_id)
    print(f"Confidence after 5 seconds (decay): {confidence2}")

    scorer.update_confidence(True, item_id=item_id)  # Positive feedback
    confidence3 = scorer.get_confidence(context=context1, item_id=item_id)
    print(f"Confidence after positive feedback: {confidence3}")

    context2 = {"user_age": 65, "item_popularity": 0.2}
    confidence4 = scorer.get_confidence(context=context2, item_id=item_id)
    print(f"Confidence with different context: {confidence4}")

    scorer.update_confidence(False, item_id=item_id)  # Negative feedback
    confidence5 = scorer.get_confidence(context=context2, item_id=item_id)
    print(f"Confidence after negative feedback: {confidence5}")

    # Demonstrate resetting confidence
    scorer.reset_confidence(0.2)
    confidence6 = scorer.get_confidence()
    print(f"Confidence after resetting: {confidence6}")
