"""Debate framework implementation for agent argumentation.

This module implements a structured debate mechanism where Bull and Bear
agents can present arguments, counter-arguments, and rebuttals to reach
a more informed consensus on trading decisions.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any, Dict, List, Optional

from loguru import logger


class ArgumentType(Enum):
    """Types of arguments in a debate."""

    BULLISH = "bullish"
    BEARISH = "bearish"
    NEUTRAL = "neutral"


class ArgumentStrength(Enum):
    """Strength levels for arguments."""

    WEAK = 1
    MODERATE = 2
    STRONG = 3
    COMPELLING = 4


@dataclass
class Argument:
    """A single argument in the debate.

    Attributes:
        agent_id: The agent making the argument
        argument_type: Type of argument (bullish/bearish/neutral)
        claim: The main claim or thesis
        evidence: Supporting evidence or reasoning
        strength: Strength level of the argument
        target_factors: Specific factors this argument addresses
    """

    agent_id: str
    argument_type: ArgumentType
    claim: str
    evidence: str
    strength: ArgumentStrength
    target_factors: List[str] = field(default_factory=list)
    timestamp: datetime = field(default_factory=datetime.now)

    def to_dict(self) -> Dict[str, Any]:
        """Convert argument to dictionary."""
        return {
            "agent_id": self.agent_id,
            "argument_type": self.argument_type.value,
            "claim": self.claim,
            "evidence": self.evidence,
            "strength": self.strength.value,
            "target_factors": self.target_factors,
            "timestamp": self.timestamp.isoformat(),
        }


@dataclass
class Rebuttal:
    """A rebuttal to an argument.

    Attributes:
        agent_id: The agent making the rebuttal
        target_argument: The argument being rebutted
        counter_claim: The counter-argument
        reasoning: Why the counter-argument is valid
        effectiveness: How effectively it counters (0.0 to 1.0)
    """

    agent_id: str
    target_argument: Argument
    counter_claim: str
    reasoning: str
    effectiveness: float = 0.5  # 0.0 to 1.0
    timestamp: datetime = field(default_factory=datetime.now)

    def __post_init__(self):
        """Validate effectiveness is in valid range."""
        self.effectiveness = max(0.0, min(1.0, self.effectiveness))

    def to_dict(self) -> Dict[str, Any]:
        """Convert rebuttal to dictionary."""
        return {
            "agent_id": self.agent_id,
            "target_argument": self.target_argument.to_dict(),
            "counter_claim": self.counter_claim,
            "reasoning": self.reasoning,
            "effectiveness": self.effectiveness,
            "timestamp": self.timestamp.isoformat(),
        }


@dataclass
class DebateRound:
    """A single round of debate.

    Attributes:
        round_number: Which round this is (1-indexed)
        arguments: Arguments presented in this round
        rebuttals: Rebuttals to arguments from previous rounds
        summary: Summary of key points from this round
    """

    round_number: int
    arguments: List[Argument] = field(default_factory=list)
    rebuttals: List[Rebuttal] = field(default_factory=list)
    summary: str = ""

    def add_argument(self, argument: Argument) -> None:
        """Add an argument to this round."""
        self.arguments.append(argument)

    def add_rebuttal(self, rebuttal: Rebuttal) -> None:
        """Add a rebuttal to this round."""
        self.rebuttals.append(rebuttal)

    def get_bullish_arguments(self) -> List[Argument]:
        """Get all bullish arguments in this round."""
        return [a for a in self.arguments if a.argument_type == ArgumentType.BULLISH]

    def get_bearish_arguments(self) -> List[Argument]:
        """Get all bearish arguments in this round."""
        return [a for a in self.arguments if a.argument_type == ArgumentType.BEARISH]

    def to_dict(self) -> Dict[str, Any]:
        """Convert round to dictionary."""
        return {
            "round_number": self.round_number,
            "arguments": [a.to_dict() for a in self.arguments],
            "rebuttals": [r.to_dict() for r in self.rebuttals],
            "summary": self.summary,
        }


@dataclass
class DebateResult:
    """Result of a completed debate.

    Attributes:
        symbol: The trading symbol debated
        winner: The winning side ("bull", "bear", "tie")
        bull_score: Bullish argument score
        bear_score: Bearish argument score
        consensus_level: How much consensus was reached (0.0 to 1.0)
        key_points: Key points agreed upon
        disagreements: Points that remained contested
        recommendation: Final trading recommendation
        confidence: Confidence in the recommendation (0.0 to 1.0)
    """

    symbol: str
    winner: str  # "bull", "bear", "tie"
    bull_score: float = 0.0
    bear_score: float = 0.0
    consensus_level: float = 0.0
    key_points: List[str] = field(default_factory=list)
    disagreements: List[str] = field(default_factory=list)
    recommendation: str = "hold"  # "buy", "sell", "hold"
    confidence: float = 0.0
    rounds_completed: int = 0
    timestamp: datetime = field(default_factory=datetime.now)

    def to_dict(self) -> Dict[str, Any]:
        """Convert result to dictionary."""
        return {
            "symbol": self.symbol,
            "winner": self.winner,
            "bull_score": round(self.bull_score, 4),
            "bear_score": round(self.bear_score, 4),
            "consensus_level": round(self.consensus_level, 4),
            "key_points": self.key_points,
            "disagreements": self.disagreements,
            "recommendation": self.recommendation,
            "confidence": round(self.confidence, 4),
            "rounds_completed": self.rounds_completed,
            "timestamp": self.timestamp.isoformat(),
        }


@dataclass
class DebateConfig:
    """Configuration for debate behavior.

    Attributes:
        max_rounds: Maximum number of debate rounds
        min_rounds: Minimum rounds before allowing early termination
        consensus_threshold: Score difference threshold for consensus
        enable_rebuttals: Whether to allow rebuttals
        time_limit_seconds: Optional time limit for debate
    """

    max_rounds: int = 3
    min_rounds: int = 2
    consensus_threshold: float = 0.3  # Score difference for consensus
    enable_rebuttals: bool = True
    time_limit_seconds: Optional[int] = None

    def __post_init__(self):
        """Validate configuration."""
        if self.max_rounds < self.min_rounds:
            raise ValueError("max_rounds must be >= min_rounds")
        if self.consensus_threshold < 0 or self.consensus_threshold > 1:
            raise ValueError("consensus_threshold must be between 0 and 1")


class DebateFramework:
    """Framework for structured agent debates.

    Manages the debate process between Bull and Bear agents,
    tracking arguments, rebuttals, and computing final consensus.
    """

    def __init__(self, config: Optional[DebateConfig] = None):
        """Initialize debate framework.

        Args:
            config: Debate configuration
        """
        self.config = config or DebateConfig()
        self.rounds: List[DebateRound] = []
        self._debate_history: List[DebateResult] = []

    def start_debate(self, symbol: str, context: Optional[Dict[str, Any]] = None) -> None:
        """Start a new debate.

        Args:
            symbol: The trading symbol to debate
            context: Optional context information
        """
        self.symbol = symbol
        self.context = context or {}
        self.rounds = []
        self.start_time = datetime.now()
        logger.info(f"Starting debate for {symbol}")

    def add_round(self) -> DebateRound:
        """Add a new debate round.

        Returns:
            The new debate round
        """
        round_num = len(self.rounds) + 1
        new_round = DebateRound(round_number=round_num)
        self.rounds.append(new_round)
        logger.debug(f"Added debate round {round_num}")
        return new_round

    def submit_argument(
        self,
        agent_id: str,
        argument_type: ArgumentType,
        claim: str,
        evidence: str,
        strength: ArgumentStrength = ArgumentStrength.MODERATE,
        target_factors: Optional[List[str]] = None,
    ) -> Argument:
        """Submit an argument to the current round.

        Args:
            agent_id: The agent making the argument
            argument_type: Type of argument
            claim: The main claim
            evidence: Supporting evidence
            strength: Strength of the argument
            target_factors: Factors this argument addresses

        Returns:
            The created argument
        """
        if not self.rounds:
            self.add_round()

        argument = Argument(
            agent_id=agent_id,
            argument_type=argument_type,
            claim=claim,
            evidence=evidence,
            strength=strength,
            target_factors=target_factors or [],
        )

        self.rounds[-1].add_argument(argument)
        logger.debug(f"Agent {agent_id} submitted {argument_type.value} argument")
        return argument

    def submit_rebuttal(
        self,
        agent_id: str,
        target_argument: Argument,
        counter_claim: str,
        reasoning: str,
        effectiveness: float = 0.5,
    ) -> Rebuttal:
        """Submit a rebuttal to an argument.

        Args:
            agent_id: The agent making the rebuttal
            target_argument: The argument being rebutted
            counter_claim: The counter-argument
            reasoning: Reasoning for the rebuttal
            effectiveness: How effective the rebuttal is (0.0 to 1.0)

        Returns:
            The created rebuttal
        """
        if not self.rounds:
            raise ValueError("No debate rounds exist")

        if not self.config.enable_rebuttals:
            logger.warning("Rebuttals are disabled in config")
            return None

        rebuttal = Rebuttal(
            agent_id=agent_id,
            target_argument=target_argument,
            counter_claim=counter_claim,
            reasoning=reasoning,
            effectiveness=effectiveness,
        )

        self.rounds[-1].add_rebuttal(rebuttal)
        logger.debug(f"Agent {agent_id} submitted rebuttal")
        return rebuttal

    def _calculate_argument_score(self, argument: Argument, round_num: int) -> float:
        """Calculate weighted score for an argument.

        Args:
            argument: The argument to score
            round_num: Which round the argument was in

        Returns:
            Weighted score
        """
        # Base score from strength
        base_score = argument.strength.value * 10

        # Earlier rounds have more weight (fresh arguments matter more)
        round_decay = 1.0 - (round_num - 1) * 0.1

        return base_score * round_decay

    def _calculate_scores(self) -> tuple[float, float]:
        """Calculate cumulative bull and bear scores.

        Returns:
            Tuple of (bull_score, bear_score)
        """
        bull_score = 0.0
        bear_score = 0.0

        for round_idx, round_data in enumerate(self.rounds):
            for argument in round_data.arguments:
                arg_score = self._calculate_argument_score(argument, round_idx + 1)

                if argument.argument_type == ArgumentType.BULLISH:
                    bull_score += arg_score
                elif argument.argument_type == ArgumentType.BEARISH:
                    bear_score += arg_score

            # Apply rebuttal effects
            for rebuttal in round_data.rebuttals:
                target = rebuttal.target_argument
                reduction = rebuttal.effectiveness * 0.5  # Max 50% reduction

                if target.argument_type == ArgumentType.BULLISH:
                    bull_score *= (1 - reduction)
                elif target.argument_type == ArgumentType.BEARISH:
                    bear_score *= (1 - reduction)

        return bull_score, bear_score

    def _check_consensus(self, bull_score: float, bear_score: float) -> bool:
        """Check if consensus has been reached.

        Args:
            bull_score: Current bull score
            bear_score: Current bear score

        Returns:
            True if consensus reached
        """
        total_score = bull_score + bear_score
        if total_score == 0:
            return False

        # Calculate score difference as ratio
        score_diff = abs(bull_score - bear_score) / total_score

        # Also check if enough rounds have passed
        if len(self.rounds) < self.config.min_rounds:
            return False

        return score_diff >= self.config.consensus_threshold

    def _extract_key_points(self) -> List[str]:
        """Extract key points that both sides agree on."""
        key_points = []

        # Find arguments mentioned by both sides
        bull_factors = set()
        bear_factors = set()

        for round_data in self.rounds:
            for arg in round_data.arguments:
                if arg.argument_type == ArgumentType.BULLISH:
                    bull_factors.update(arg.target_factors)
                elif arg.argument_type == ArgumentType.BEARISH:
                    bear_factors.update(arg.target_factors)

        # Common factors indicate some agreement
        common = bull_factors & bear_factors
        for factor in common:
            key_points.append(f"Both sides acknowledge: {factor}")

        return key_points

    def _extract_disagreements(self) -> List[str]:
        """Extract points of disagreement."""
        disagreements = []

        # Look for rebuttals to identify disagreements
        for round_data in self.rounds:
            for rebuttal in round_data.rebuttals:
                target_claim = rebuttal.target_argument.claim
                disagreements.append(f"Contested: {target_claim[:50]}...")

        return list(set(disagreements))  # Remove duplicates

    def should_continue(self) -> bool:
        """Check if debate should continue.

        Returns:
            True if more rounds should be conducted
        """
        # Check max rounds
        if len(self.rounds) >= self.config.max_rounds:
            return False

        # Check time limit
        if self.config.time_limit_seconds:
            elapsed = (datetime.now() - self.start_time).total_seconds()
            if elapsed >= self.config.time_limit_seconds:
                return False

        # Check consensus
        bull_score, bear_score = self._calculate_scores()
        if self._check_consensus(bull_score, bear_score):
            return False

        return True

    def conclude_debate(self) -> DebateResult:
        """Conclude the debate and return results.

        Returns:
            DebateResult with final scores and recommendation
        """
        bull_score, bear_score = self._calculate_scores()

        # Determine winner
        if bull_score > bear_score * 1.2:
            winner = "bull"
        elif bear_score > bull_score * 1.2:
            winner = "bear"
        else:
            winner = "tie"

        # Calculate consensus level
        total = bull_score + bear_score
        if total > 0:
            consensus_level = abs(bull_score - bear_score) / total
        else:
            consensus_level = 0.0

        # Generate recommendation
        if winner == "bull":
            recommendation = "buy"
            confidence = min(0.5 + consensus_level * 0.5, 0.95)
        elif winner == "bear":
            recommendation = "sell"
            confidence = min(0.5 + consensus_level * 0.5, 0.95)
        else:
            recommendation = "hold"
            confidence = 0.5

        result = DebateResult(
            symbol=self.symbol,
            winner=winner,
            bull_score=bull_score,
            bear_score=bear_score,
            consensus_level=consensus_level,
            key_points=self._extract_key_points(),
            disagreements=self._extract_disagreements(),
            recommendation=recommendation,
            confidence=confidence,
            rounds_completed=len(self.rounds),
        )

        self._debate_history.append(result)
        logger.info(
            f"Debate concluded for {self.symbol}: {winner} wins "
            f"(bull={bull_score:.2f}, bear={bear_score:.2f})"
        )

        return result

    def get_debate_history(self) -> List[DebateResult]:
        """Get history of all debates."""
        return self._debate_history.copy()

    def get_latest_debate(self, symbol: str) -> Optional[DebateResult]:
        """Get the most recent debate for a symbol.

        Args:
            symbol: Trading symbol to look up

        Returns:
            Most recent DebateResult or None
        """
        for result in reversed(self._debate_history):
            if result.symbol == symbol:
                return result
        return None
