"""
Conflict Resolver

This module provides comprehensive conflict resolution capabilities for the
Semantica framework, offering multiple strategies for resolving detected conflicts
including voting mechanisms, credibility-based resolution, and expert review.

Algorithms Used:

Voting Algorithm:
    - Majority Value Selection: Uses Counter-based frequency counting to select
      the most frequently occurring value across sources
    - Tie Breaking: Handles ties by selecting first value or using confidence scores

Credibility Weighted Resolution:
    - Weighted Average Calculation: Calculates weighted average of values based on
      source credibility scores
    - Credibility Scoring: Uses source credibility from SourceTracker
    - Value Weight Calculation: Combines value frequency with source credibility

Temporal Selection:
    - Most Recent: Timestamp-based selection of newest value
    - First Seen: Temporal-based selection of oldest value
    - Timestamp Comparison: Compares source timestamps to determine temporal order

Confidence Selection:
    - Maximum Confidence: Selects value from source with highest confidence score
    - Confidence Comparison: Compares source confidence values

Manual/Expert Review:
    - Conflict Flagging: Flags conflicts for human review workflow
    - Review Queue Management: Manages conflicts requiring manual intervention

Key Features:
    - Automatic conflict resolution strategies
    - Voting-based resolution from multiple sources
    - Credibility-weighted conflict resolution
    - Manual conflict resolution workflow
    - Resolution rule configuration
    - Conflict resolution history tracking
    - Multiple resolution strategies (voting, credibility, recency, confidence)

Main Classes:
    - ResolutionStrategy: Conflict resolution strategy enumeration
    - ResolutionResult: Conflict resolution result data structure
    - ConflictResolver: Conflict resolver with multiple resolution strategies

Example Usage:
    >>> from semantica.conflicts import ConflictResolver
    >>> resolver = ConflictResolver()
    >>> result = resolver.resolve_conflict(conflict, strategy="voting")
    >>> results = resolver.resolve_conflicts(conflicts, strategy="voting")

Author: Semantica Contributors
License: MIT
"""

from collections import Counter
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable, Dict, List, Optional, Union

from ..utils.exceptions import ProcessingError, ValidationError
from ..utils.logging import get_logger
from ..utils.progress_tracker import get_progress_tracker
from .conflict_detector import Conflict, ConflictType
from .source_tracker import SourceTracker


class ResolutionStrategy(str, Enum):
    """Conflict resolution strategy."""

    VOTING = "voting"
    CREDIBILITY_WEIGHTED = "credibility_weighted"
    MOST_RECENT = "most_recent"
    FIRST_SEEN = "first_seen"
    HIGHEST_CONFIDENCE = "highest_confidence"
    MANUAL_REVIEW = "manual_review"
    EXPERT_REVIEW = "expert_review"


@dataclass
class ResolutionResult:
    """Conflict resolution result."""

    conflict_id: str
    resolved: bool
    resolved_value: Any = None
    resolution_strategy: Optional[str] = None
    confidence: float = 0.0
    sources_used: List[str] = field(default_factory=list)
    resolution_notes: Optional[str] = None
    metadata: Dict[str, Any] = field(default_factory=dict)


class ConflictResolver:
    """
    Conflict resolver with multiple resolution strategies.

    • Automatic conflict resolution strategies
    • Voting-based resolution from multiple sources
    • Credibility-weighted conflict resolution
    • Manual conflict resolution workflow
    • Resolution rule configuration
    • Conflict resolution history tracking
    """

    def __init__(self, config: Optional[Dict[str, Any]] = None, **kwargs):
        """
        Initialize conflict resolver.

        Args:
            config: Configuration dictionary
            **kwargs: Additional configuration options:
                - default_strategy: Default resolution strategy
                - source_tracker: Source tracker instance
                - resolution_rules: Custom resolution rules
        """
        self.logger = get_logger("conflict_resolver")
        self.config = config or {}
        self.config.update(kwargs)

        self.source_tracker = self.config.get("source_tracker") or SourceTracker()
        self.default_strategy = ResolutionStrategy(
            self.config.get("default_strategy", "voting")
        )
        self.resolution_rules: Dict[str, ResolutionStrategy] = self.config.get(
            "resolution_rules", {}
        )

        # Initialize progress tracker
        self.progress_tracker = get_progress_tracker()

        self.resolution_history: List[ResolutionResult] = []

    def _normalize_strategy(
        self, strategy: Union[str, ResolutionStrategy, None]
    ) -> ResolutionStrategy:
        """
        Normalize strategy to ResolutionStrategy enum.

        Args:
            strategy: Strategy as string, ResolutionStrategy enum, or None

        Returns:
            ResolutionStrategy enum

        Raises:
            ValueError: If string strategy is invalid
            TypeError: If strategy is not str, ResolutionStrategy, or None
        """
        if strategy is None:
            return self.default_strategy
        if isinstance(strategy, ResolutionStrategy):
            return strategy
        if isinstance(strategy, str):
            try:
                return ResolutionStrategy(strategy)
            except ValueError:
                valid_strategies = [s.value for s in ResolutionStrategy]
                raise ValueError(
                    f"Invalid strategy: {strategy}. "
                    f"Valid strategies: {valid_strategies}"
                )
        raise TypeError(
            f"Strategy must be str or ResolutionStrategy, got {type(strategy)}"
        )

    def resolve_conflict(
        self, conflict: Conflict, strategy: Union[str, ResolutionStrategy, None] = None
    ) -> ResolutionResult:
        """
        Resolve a conflict using specified strategy.

        Args:
            conflict: Conflict to resolve
            strategy: Resolution strategy as string (e.g., "voting"),
                     ResolutionStrategy enum, or None (uses default).
                     Valid strings: "voting", "credibility_weighted",
                     "most_recent", "first_seen", "highest_confidence",
                     "manual_review", "expert_review"

        Returns:
            Resolution result

        Examples:
            >>> resolver = ConflictResolver()
            >>> result = resolver.resolve_conflict(conflict, strategy="voting")
        """
        # Track conflict resolution
        tracking_id = self.progress_tracker.start_tracking(
            file=None,
            module="conflicts",
            submodule="ConflictResolver",
            message=f"Resolving conflict: {conflict.conflict_id}",
        )

        try:
            # Normalize strategy (handles None, string, or enum)
            normalized_strategy = self._normalize_strategy(strategy)

            # Check for property-specific rule if strategy was None
            if strategy is None:
                if conflict.property_name:
                    rule_key = f"{conflict.entity_id}.{conflict.property_name}"
                    if rule_key in self.resolution_rules:
                        normalized_strategy = self.resolution_rules[rule_key]

            strategy = normalized_strategy

            self.logger.info(
                f"Resolving conflict {conflict.conflict_id} using strategy: {strategy.value}"
            )

            if strategy == ResolutionStrategy.VOTING:
                result = self._resolve_by_voting(conflict)
            elif strategy == ResolutionStrategy.CREDIBILITY_WEIGHTED:
                result = self._resolve_by_credibility(conflict)
            elif strategy == ResolutionStrategy.MOST_RECENT:
                result = self._resolve_by_recency(conflict)
            elif strategy == ResolutionStrategy.FIRST_SEEN:
                result = self._resolve_by_first_seen(conflict)
            elif strategy == ResolutionStrategy.HIGHEST_CONFIDENCE:
                result = self._resolve_by_confidence(conflict)
            elif strategy == ResolutionStrategy.MANUAL_REVIEW:
                result = self._flag_for_manual_review(conflict)
            elif strategy == ResolutionStrategy.EXPERT_REVIEW:
                result = self._flag_for_expert_review(conflict)
            else:
                result = self._resolve_by_voting(conflict)

            result.resolution_strategy = strategy.value
            self.resolution_history.append(result)

            self.progress_tracker.stop_tracking(
                tracking_id,
                status="completed",
                message=f"Resolved conflict using {strategy.value}",
            )
            return result

        except Exception as e:
            self.progress_tracker.stop_tracking(
                tracking_id, status="failed", message=str(e)
            )
            raise

    def resolve_conflicts(
        self,
        conflicts: List[Conflict],
        strategy: Union[str, ResolutionStrategy, None] = None,
    ) -> List[ResolutionResult]:
        """
        Resolve multiple conflicts.

        Args:
            conflicts: List of conflicts to resolve
            strategy: Resolution strategy as string (e.g., "voting"),
                     ResolutionStrategy enum, or None (uses default).
                     Valid strings: "voting", "credibility_weighted",
                     "most_recent", "first_seen", "highest_confidence",
                     "manual_review", "expert_review"

        Returns:
            List of resolution results

        Examples:
            >>> resolver = ConflictResolver()
            >>> results = resolver.resolve_conflicts(conflicts, strategy="voting")
        """
        results = []

        for conflict in conflicts:
            result = self.resolve_conflict(conflict, strategy)
            results.append(result)

        return results

    def _resolve_by_voting(self, conflict: Conflict) -> ResolutionResult:
        """Resolve conflict by voting (most common value wins)."""
        if not conflict.conflicting_values:
            return ResolutionResult(
                conflict_id=conflict.conflict_id,
                resolved=False,
                resolution_notes="No conflicting values to resolve",
            )

        # Count value occurrences
        value_counts = Counter(conflict.conflicting_values)
        most_common_value, count = value_counts.most_common(1)[0]

        # Calculate confidence based on vote ratio
        total_votes = len(conflict.conflicting_values)
        confidence = count / total_votes if total_votes > 0 else 0.0

        sources_used = [s.get("document", "unknown") for s in conflict.sources]

        return ResolutionResult(
            conflict_id=conflict.conflict_id,
            resolved=True,
            resolved_value=most_common_value,
            confidence=confidence,
            sources_used=sources_used,
            resolution_notes=f"Resolved by voting: {count}/{total_votes} votes for this value",
        )

    def _resolve_by_credibility(self, conflict: Conflict) -> ResolutionResult:
        """Resolve conflict by credibility-weighted voting."""
        if not conflict.conflicting_values or not conflict.sources:
            return ResolutionResult(
                conflict_id=conflict.conflict_id,
                resolved=False,
                resolution_notes="Insufficient data for credibility-based resolution",
            )

        # Weight values by source credibility
        value_weights: Dict[Any, float] = {}

        for i, value in enumerate(conflict.conflicting_values):
            source = conflict.sources[i] if i < len(conflict.sources) else {}
            document = source.get("document", "unknown")
            source_confidence = source.get("confidence", 0.5)
            credibility = self.source_tracker.get_source_credibility(document)

            weight = source_confidence * credibility

            if value not in value_weights:
                value_weights[value] = 0.0
            value_weights[value] += weight

        # Get value with highest weight
        if not value_weights:
            return ResolutionResult(
                conflict_id=conflict.conflict_id,
                resolved=False,
                resolution_notes="Could not calculate credibility weights",
            )

        resolved_value = max(value_weights.items(), key=lambda x: x[1])[0]
        total_weight = sum(value_weights.values())
        confidence = (
            value_weights[resolved_value] / total_weight if total_weight > 0 else 0.0
        )

        sources_used = [s.get("document", "unknown") for s in conflict.sources]

        return ResolutionResult(
            conflict_id=conflict.conflict_id,
            resolved=True,
            resolved_value=resolved_value,
            confidence=confidence,
            sources_used=sources_used,
            resolution_notes=f"Resolved by credibility-weighted voting (weight: {value_weights[resolved_value]:.2f})",
        )

    def _resolve_by_recency(self, conflict: Conflict) -> ResolutionResult:
        """Resolve conflict by using most recent value."""
        if not conflict.sources:
            return ResolutionResult(
                conflict_id=conflict.conflict_id,
                resolved=False,
                resolution_notes="No source timestamps available",
            )

        # Find most recent source (by timestamp if available)
        most_recent_idx = 0
        most_recent_time = None

        for i, source in enumerate(conflict.sources):
            # Check metadata for timestamp
            timestamp = source.get("metadata", {}).get("timestamp")
            if timestamp:
                if not most_recent_time or timestamp > most_recent_time:
                    most_recent_time = timestamp
                    most_recent_idx = i

        resolved_value = conflict.conflicting_values[most_recent_idx]
        sources_used = [conflict.sources[most_recent_idx].get("document", "unknown")]

        return ResolutionResult(
            conflict_id=conflict.conflict_id,
            resolved=True,
            resolved_value=resolved_value,
            confidence=0.8,
            sources_used=sources_used,
            resolution_notes="Resolved by most recent value",
        )

    def _resolve_by_first_seen(self, conflict: Conflict) -> ResolutionResult:
        """Resolve conflict by using first seen value."""
        if not conflict.conflicting_values:
            return ResolutionResult(conflict_id=conflict.conflict_id, resolved=False)

        resolved_value = conflict.conflicting_values[0]
        sources_used = (
            [conflict.sources[0].get("document", "unknown")] if conflict.sources else []
        )

        return ResolutionResult(
            conflict_id=conflict.conflict_id,
            resolved=True,
            resolved_value=resolved_value,
            confidence=0.7,
            sources_used=sources_used,
            resolution_notes="Resolved by first seen value",
        )

    def _resolve_by_confidence(self, conflict: Conflict) -> ResolutionResult:
        """Resolve conflict by using value with highest confidence."""
        if not conflict.sources:
            return ResolutionResult(
                conflict_id=conflict.conflict_id,
                resolved=False,
                resolution_notes="No source confidence data available",
            )

        # Find source with highest confidence
        max_confidence = 0.0
        best_idx = 0

        for i, source in enumerate(conflict.sources):
            confidence = source.get("confidence", 0.0)
            if confidence > max_confidence:
                max_confidence = confidence
                best_idx = i

        resolved_value = conflict.conflicting_values[best_idx]
        sources_used = [conflict.sources[best_idx].get("document", "unknown")]

        return ResolutionResult(
            conflict_id=conflict.conflict_id,
            resolved=True,
            resolved_value=resolved_value,
            confidence=max_confidence,
            sources_used=sources_used,
            resolution_notes=f"Resolved by highest confidence ({max_confidence:.2f})",
        )

    def _flag_for_manual_review(self, conflict: Conflict) -> ResolutionResult:
        """Flag conflict for manual review."""
        return ResolutionResult(
            conflict_id=conflict.conflict_id,
            resolved=False,
            resolution_notes="Flagged for manual review",
            metadata={"requires_manual_review": True, "severity": conflict.severity},
        )

    def _flag_for_expert_review(self, conflict: Conflict) -> ResolutionResult:
        """Flag conflict for expert review."""
        return ResolutionResult(
            conflict_id=conflict.conflict_id,
            resolved=False,
            resolution_notes="Flagged for expert review",
            metadata={"requires_expert_review": True, "severity": conflict.severity},
        )

    def set_resolution_rule(
        self,
        entity_id: str,
        property_name: str,
        strategy: Union[str, ResolutionStrategy],
    ) -> bool:
        """
        Set custom resolution rule for specific property.

        Args:
            entity_id: Entity identifier
            property_name: Property name
            strategy: Resolution strategy as string (e.g., "voting") or
                     ResolutionStrategy enum

        Returns:
            True if rule set successfully

        Examples:
            >>> resolver.set_resolution_rule("entity_1", "name", "voting")
        """
        rule_key = f"{entity_id}.{property_name}"
        normalized_strategy = self._normalize_strategy(strategy)
        self.resolution_rules[rule_key] = normalized_strategy
        self.logger.info(
            f"Set resolution rule: {rule_key} -> {normalized_strategy.value}"
        )
        return True

    def get_resolution_history(self) -> List[ResolutionResult]:
        """
        Get conflict resolution history.

        Returns:
            List of resolution results
        """
        return self.resolution_history.copy()

    def get_resolution_statistics(self) -> Dict[str, Any]:
        """
        Get resolution statistics.

        Returns:
            Statistics dictionary
        """
        total = len(self.resolution_history)
        resolved = sum(1 for r in self.resolution_history if r.resolved)

        by_strategy = {}
        for result in self.resolution_history:
            strategy = result.resolution_strategy or "unknown"
            by_strategy[strategy] = by_strategy.get(strategy, 0) + 1

        return {
            "total_resolutions": total,
            "resolved_count": resolved,
            "unresolved_count": total - resolved,
            "resolution_rate": resolved / total if total > 0 else 0.0,
            "by_strategy": by_strategy,
        }
