"""
Value assessment system for Squad AI Compound Interest.

Implements 5-factor scoring mechanism to determine knowledge value:
1. Reusability (30%): How often can this be applied?
2. Importance (25%): How critical is this knowledge?
3. Novelty (20%): How unique or non-obvious is this?
4. Accuracy (15%): How reliable and correct is this?
5. Frequency (10%): How often is this actually used?
"""

from typing import Dict, Any, Optional
from datetime import datetime


class ValueAssessor:
    """Assess the value of knowledge items using 5-factor scoring."""

    # Scoring weights (must sum to 1.0)
    WEIGHTS = {
        'reusability': 0.30,
        'importance': 0.25,
        'novelty': 0.20,
        'accuracy': 0.15,
        'frequency': 0.10
    }

    # Minimum score thresholds
    THRESHOLD_CRITICAL = 0.80
    THRESHOLD_HIGH = 0.60
    THRESHOLD_MEDIUM = 0.40
    THRESHOLD_LOW = 0.20

    @staticmethod
    def assess(
        factors: Dict[str, float],
        metadata: Optional[Dict[str, Any]] = None
    ) -> float:
        """
        Calculate composite score from 5 factors.

        Args:
            factors: Dictionary with keys: reusability, importance,
                    novelty, accuracy, frequency (values 0.0-1.0)
            metadata: Optional metadata for special rules
                     (user_correction, critical, etc.)

        Returns:
            float: Composite score (0.0-1.0)

        Raises:
            ValueError: If factors are invalid
        """
        # Validate factors
        ValueAssessor._validate_factors(factors)

        # Calculate base score
        composite_score = sum(
            factors.get(factor, 0.0) * weight
            for factor, weight in ValueAssessor.WEIGHTS.items()
        )

        # Apply special rules
        if metadata:
            composite_score = ValueAssessor._apply_special_rules(
                composite_score,
                metadata
            )

        return round(composite_score, 2)

    @staticmethod
    def _validate_factors(factors: Dict[str, float]) -> None:
        """
        Validate factor values.

        Args:
            factors: Factor dictionary

        Raises:
            ValueError: If factors are invalid
        """
        required_factors = set(ValueAssessor.WEIGHTS.keys())
        provided_factors = set(factors.keys())

        # Check all required factors are present
        missing = required_factors - provided_factors
        if missing:
            raise ValueError(
                f"Missing required factors: {missing}. "
                f"Required: {required_factors}"
            )

        # Check all values are in valid range
        for factor, value in factors.items():
            if factor in required_factors:
                if not (0.0 <= value <= 1.0):
                    raise ValueError(
                        f"Factor '{factor}' value {value} out of range [0.0, 1.0]"
                    )

    @staticmethod
    def _apply_special_rules(
        score: float,
        metadata: Dict[str, Any]
    ) -> float:
        """
        Apply special scoring rules based on metadata.

        Special rules:
        1. User corrections: Boost to min 0.80 (user explicitly fixed something)
        2. Critical knowledge: Boost to min 0.85 (marked as critical)
        3. Frequent errors: Boost to min 0.75 (repeated mistakes)
        4. High usage: Increase by 0.10 (used >10 times in last month)

        Args:
            score: Base composite score
            metadata: Metadata dictionary

        Returns:
            float: Adjusted score
        """
        # Rule 1: User corrections
        if metadata.get('user_correction', False):
            score = max(score, 0.80)

        # Rule 2: Critical knowledge
        if metadata.get('critical', False):
            score = max(score, 0.85)

        # Rule 3: Frequent errors (from lessons)
        if metadata.get('error_count', 0) >= 3:
            score = max(score, 0.75)

        # Rule 4: High usage
        usage_count = metadata.get('usage_count', 0)
        if usage_count >= 10:
            score = min(score + 0.10, 1.0)

        return score

    @staticmethod
    def assess_reusability(
        scope: str = "project",
        applicability: str = "specific",
        generality: str = "low"
    ) -> float:
        """
        Assess reusability factor.

        Args:
            scope: Scope of applicability (session, project, team, universal)
            applicability: How broadly applicable (specific, moderate, broad)
            generality: How general the knowledge is (low, medium, high)

        Returns:
            float: Reusability score (0.0-1.0)
        """
        # Scope scoring
        scope_scores = {
            'session': 0.2,
            'project': 0.4,
            'team': 0.7,
            'universal': 1.0
        }

        # Applicability scoring
        applicability_scores = {
            'specific': 0.3,
            'moderate': 0.6,
            'broad': 1.0
        }

        # Generality scoring
        generality_scores = {
            'low': 0.3,
            'medium': 0.6,
            'high': 1.0
        }

        # Weighted average
        score = (
            scope_scores.get(scope, 0.4) * 0.4 +
            applicability_scores.get(applicability, 0.6) * 0.3 +
            generality_scores.get(generality, 0.6) * 0.3
        )

        return round(score, 2)

    @staticmethod
    def assess_importance(
        impact: str = "medium",
        urgency: str = "normal",
        consequences: str = "minor"
    ) -> float:
        """
        Assess importance factor.

        Args:
            impact: Impact level (low, medium, high, critical)
            urgency: Urgency level (low, normal, high, immediate)
            consequences: Consequences of not knowing (none, minor, major, severe)

        Returns:
            float: Importance score (0.0-1.0)
        """
        # Impact scoring
        impact_scores = {
            'low': 0.2,
            'medium': 0.5,
            'high': 0.8,
            'critical': 1.0
        }

        # Urgency scoring
        urgency_scores = {
            'low': 0.2,
            'normal': 0.5,
            'high': 0.8,
            'immediate': 1.0
        }

        # Consequences scoring
        consequences_scores = {
            'none': 0.0,
            'minor': 0.3,
            'major': 0.7,
            'severe': 1.0
        }

        # Weighted average
        score = (
            impact_scores.get(impact, 0.5) * 0.5 +
            urgency_scores.get(urgency, 0.5) * 0.2 +
            consequences_scores.get(consequences, 0.3) * 0.3
        )

        return round(score, 2)

    @staticmethod
    def assess_novelty(
        uniqueness: str = "common",
        insight_level: str = "obvious",
        documentation: str = "well_documented"
    ) -> float:
        """
        Assess novelty factor.

        Args:
            uniqueness: How unique (common, uncommon, rare, unique)
            insight_level: Level of insight (obvious, useful, insightful, breakthrough)
            documentation: Documentation status (well_documented, sparse, undocumented)

        Returns:
            float: Novelty score (0.0-1.0)
        """
        # Uniqueness scoring
        uniqueness_scores = {
            'common': 0.2,
            'uncommon': 0.5,
            'rare': 0.8,
            'unique': 1.0
        }

        # Insight level scoring
        insight_scores = {
            'obvious': 0.2,
            'useful': 0.5,
            'insightful': 0.8,
            'breakthrough': 1.0
        }

        # Documentation status (inverse - less docs = more novel)
        documentation_scores = {
            'well_documented': 0.2,
            'sparse': 0.6,
            'undocumented': 1.0
        }

        # Weighted average
        score = (
            uniqueness_scores.get(uniqueness, 0.5) * 0.4 +
            insight_scores.get(insight_level, 0.5) * 0.4 +
            documentation_scores.get(documentation, 0.6) * 0.2
        )

        return round(score, 2)

    @staticmethod
    def assess_accuracy(
        verification: str = "tested",
        source_reliability: str = "medium",
        confidence: str = "medium"
    ) -> float:
        """
        Assess accuracy factor.

        Args:
            verification: Verification status (untested, tested, verified, validated)
            source_reliability: Source reliability (low, medium, high, expert)
            confidence: Confidence level (low, medium, high, certain)

        Returns:
            float: Accuracy score (0.0-1.0)
        """
        # Verification scoring
        verification_scores = {
            'untested': 0.3,
            'tested': 0.6,
            'verified': 0.8,
            'validated': 1.0
        }

        # Source reliability scoring
        source_scores = {
            'low': 0.3,
            'medium': 0.6,
            'high': 0.8,
            'expert': 1.0
        }

        # Confidence scoring
        confidence_scores = {
            'low': 0.3,
            'medium': 0.6,
            'high': 0.8,
            'certain': 1.0
        }

        # Weighted average
        score = (
            verification_scores.get(verification, 0.6) * 0.4 +
            source_scores.get(source_reliability, 0.6) * 0.3 +
            confidence_scores.get(confidence, 0.6) * 0.3
        )

        return round(score, 2)

    @staticmethod
    def assess_frequency(
        usage_count: int = 0,
        time_period_days: int = 30,
        last_used_days: Optional[int] = None
    ) -> float:
        """
        Assess frequency factor.

        Args:
            usage_count: Number of times used
            time_period_days: Time period for usage count (default: 30 days)
            last_used_days: Days since last use (None if never used)

        Returns:
            float: Frequency score (0.0-1.0)
        """
        # Calculate usage rate (uses per month)
        if time_period_days > 0:
            usage_per_month = (usage_count / time_period_days) * 30
        else:
            usage_per_month = 0

        # Usage rate scoring (logarithmic scale)
        if usage_per_month >= 30:  # Daily
            usage_score = 1.0
        elif usage_per_month >= 10:  # Multiple times per week
            usage_score = 0.8
        elif usage_per_month >= 4:  # Weekly
            usage_score = 0.6
        elif usage_per_month >= 1:  # Monthly
            usage_score = 0.4
        elif usage_count > 0:  # Rarely
            usage_score = 0.2
        else:  # Never
            usage_score = 0.0

        # Recency penalty
        if last_used_days is not None:
            if last_used_days <= 7:
                recency_factor = 1.0
            elif last_used_days <= 30:
                recency_factor = 0.8
            elif last_used_days <= 90:
                recency_factor = 0.5
            else:
                recency_factor = 0.2

            usage_score *= recency_factor

        return round(usage_score, 2)

    @staticmethod
    def get_value_tier(score: float) -> str:
        """
        Get value tier from score.

        Args:
            score: Composite score (0.0-1.0)

        Returns:
            str: Value tier (critical, high, medium, low)
        """
        if score >= ValueAssessor.THRESHOLD_CRITICAL:
            return "critical"
        elif score >= ValueAssessor.THRESHOLD_HIGH:
            return "high"
        elif score >= ValueAssessor.THRESHOLD_MEDIUM:
            return "medium"
        else:
            return "low"
