"""Pydantic models for LLM-First Debugging (Plan 80)."""

from __future__ import annotations

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

from pydantic import BaseModel, Field

# =============================================================================
# Core Enums
# =============================================================================

class FlakinessCategory(str, Enum):
    """Categories of flakiness root causes.

    Aligned with the 10-category taxonomy from Luo et al. FSE 2014
    plus UNKNOWN.  All three FlakinessCategory enums in the codebase
    should have the same members (E6/F-11).
    """
    TIMING = "timing"
    ORDERING = "ordering"
    RESOURCE = "resource"
    STATE = "state"
    NETWORK = "network"
    CONCURRENCY = "concurrency"
    FLOATING_POINT = "floating_point"
    RANDOMNESS = "randomness"
    PLATFORM = "platform"
    ENVIRONMENT = "environment"
    UNKNOWN = "unknown"


class FixStrategy(str, Enum):
    """Fix generation strategies."""
    CONSERVATIVE = "conservative"
    RAG_BASED = "rag_based"
    FRAMEWORK_SPECIFIC = "framework_specific"
    CREATIVE = "creative"
    ENSEMBLE = "ensemble"
    TEMPLATE_BASED = "template_based"


class RefinementStage(str, Enum):
    """Stages in fix refinement."""
    SYNTAX = "syntax"
    TYPES = "types"
    TEST = "test"
    VERIFY = "verify"


class FixOutcomeStatus(str, Enum):
    """Status of fix outcome."""
    ACCEPTED = "accepted"
    REJECTED = "rejected"
    MODIFIED = "modified"


# =============================================================================
# Core Context Models
# =============================================================================

class FailureContext(BaseModel):
    """Rich context about a test failure."""

    run_id: str
    test_name: str
    test_file: str
    function_name: Optional[str] = None
    line_number: Optional[int] = None

    # Error details
    error_type: str
    error_message: str
    stack_trace: str

    # Code context
    code_snippet: Optional[str] = None
    surrounding_code: Optional[str] = None

    # Captured data
    locals_snapshot: Optional[Dict[str, Any]] = None
    divergence_point: Optional[Dict[str, Any]] = None
    transcript_summary: Optional[str] = None

    # Embeddings
    embedding: Optional[List[float]] = None

    # Root cause (if analyzed)
    root_cause: Optional[RootCauseExplanation] = None

    # Metadata
    category: Optional[FlakinessCategory] = None
    framework: Optional[str] = None


class SuspiciousLocation(BaseModel):
    """A suspicious code location for fault localization."""

    id: str
    file: str
    line: int
    function_name: Optional[str] = None

    # Code context
    code_snippet: str

    # Scores
    sbfl_score: float = 0.0
    llm_score: float = 0.0
    combined_score: float = 0.0

    # Reasoning
    sbfl_formula: str = "ochiai"
    llm_reasoning: Optional[str] = None

    # Execution data
    pass_count: int = 0
    fail_count: int = 0


# =============================================================================
# RAG and Embedding Models
# =============================================================================

class FixEmbedding(BaseModel):
    """Embedded representation of a fix."""

    fix_id: str

    # Individual embeddings
    error_embedding: List[float]
    code_before_embedding: Optional[List[float]] = None
    code_after_embedding: Optional[List[float]] = None
    description_embedding: Optional[List[float]] = None

    # Combined embedding
    combined_embedding: List[float]

    # Metadata
    metadata: Dict[str, Any] = Field(default_factory=dict)


class SimilarFix(BaseModel):
    """A similar historical fix from RAG search."""

    fix_id: str
    similarity_score: float

    # Fix details
    error_type: str
    category: FlakinessCategory
    description: str
    diff: str

    # Quality metrics
    verification_pass_rate: float = 1.0
    user_acceptance_rate: Optional[float] = None
    times_successful: int = 0

    # Context
    original_error_message: Optional[str] = None
    strategy: Optional[FixStrategy] = None


class FailureQuery(BaseModel):
    """Query for searching similar fixes."""

    error_type: str
    error_message: Optional[str] = None
    category: Optional[FlakinessCategory] = None
    embedding: Optional[List[float]] = None
    framework: Optional[str] = None


class RankingResponse(BaseModel):
    """Response from LLM re-ranking."""

    rankings: List[str]
    reasoning: Optional[Dict[str, str]] = None


# =============================================================================
# Multi-Agent Models
# =============================================================================

class AgentHypothesis(BaseModel):
    """A hypothesis from a specialist agent."""

    agent_name: str
    category: FlakinessCategory

    # Core hypothesis
    summary: str
    detailed_explanation: str

    # Evidence
    evidence: List[str]
    supporting_code: Optional[str] = None

    # Confidence and predictions
    confidence: float = Field(ge=0.0, le=1.0)
    testable_predictions: List[str] = Field(default_factory=list)

    # Suggested fix direction
    fix_direction: Optional[str] = None


class Critique(BaseModel):
    """Critique of a hypothesis by another agent."""

    critic_agent: str
    target_hypothesis: str

    # Critique content
    strengths: List[str]
    weaknesses: List[str]
    counter_evidence: List[str]

    # Assessment
    agreement_score: float = Field(ge=0.0, le=1.0)  # 0-1 how much critic agrees
    suggested_refinement: Optional[str] = None


class JudgmentResponse(BaseModel):
    """Response from judge evaluating hypotheses."""

    winner_index: int
    winner_confidence: float

    # Reasoning
    reasoning: str
    evidence_alignment: Dict[str, float]

    # Dissent
    dissenting_points: List[str] = Field(default_factory=list)


class RootCauseExplanation(BaseModel):
    """Comprehensive root cause explanation."""

    # Core explanation
    summary: str
    category: FlakinessCategory
    confidence: float

    # Evidence
    evidence: List[str]
    divergence_location: Optional[str] = None
    code_snippet: Optional[str] = None

    # Suggested fixes
    recommended_fixes: List[str] = Field(default_factory=list)

    # Debug process
    reasoning_chain: Optional[str] = None
    debate_transcript: Optional[str] = None
    dissenting_opinions: List[AgentHypothesis] = Field(default_factory=list)

    # Metadata
    agent_count: int = 1
    tokens_used: Optional[int] = None


# =============================================================================
# Fix Generation Models
# =============================================================================

class FixHypothesis(BaseModel):
    """A fix hypothesis from multi-hypothesis generation."""

    id: str = ""
    strategy: FixStrategy

    # The fix
    diff: str
    patched_code: Optional[str] = None
    description: str

    # Quality signals
    confidence: float = Field(ge=0.0, le=1.0)
    quality_score: float = 0.0

    # Tracking
    iteration: int = 0
    refinement_reason: Optional[str] = None
    based_on: List[str] = Field(default_factory=list)

    # Validation status
    syntax_valid: bool = False
    types_valid: bool = False
    test_passed: bool = False
    verified_deterministic: bool = False


class FixQualityScores(BaseModel):
    """Quality scores for a fix hypothesis."""

    syntax_valid: float = 0.0
    types_valid: float = 0.0
    imports_valid: float = 0.0

    addresses_root_cause: float = 0.0
    minimal_change: float = 0.0
    style_consistent: float = 0.0

    similar_fix_success_rate: float = 0.0
    pattern_confidence: float = 0.0

    # Combined score
    total: float = 0.0


class RootCauseScore(BaseModel):
    """Score for whether fix addresses root cause."""

    score: float
    reasoning: str
    red_flags: List[str] = Field(default_factory=list)
    green_flags: List[str] = Field(default_factory=list)


# =============================================================================
# Refinement Models
# =============================================================================

class RefinementResult(BaseModel):
    """Result of a refinement iteration."""

    stage: RefinementStage
    input_fix: FixHypothesis
    output_fix: FixHypothesis

    # Feedback
    error_message: Optional[str] = None
    failure_output: Optional[str] = None

    # Status
    success: bool
    reasoning: str


class VerificationResult(BaseModel):
    """Result of determinism verification."""

    total_runs: int
    passed_runs: int
    failed_runs: int

    is_deterministic: bool
    failure_patterns: List[str] = Field(default_factory=list)

    run_details: List[Dict[str, Any]] = Field(default_factory=list)


# =============================================================================
# Classification Models
# =============================================================================

class TestFeatures(BaseModel):
    """Features extracted from test code for classification."""

    # Code patterns
    has_sleep: bool = False
    has_random: bool = False
    has_threading: bool = False
    has_async: bool = False
    has_network: bool = False
    has_db: bool = False

    # SQL patterns
    sql_without_order: bool = False

    # Historical signals
    pass_fail_ratio: Optional[float] = None
    failure_variance: Optional[float] = None
    ci_local_difference: Optional[float] = None

    # Complexity
    lines_of_code: int = 0
    cyclomatic_complexity: Optional[int] = None
    dependency_count: int = 0


class FlakinessPrediction(BaseModel):
    """Prediction of test flakiness."""

    is_flaky: bool
    flaky_probability: float

    category: FlakinessCategory
    category_confidence: float

    explanation: str
    risk_factors: List[str] = Field(default_factory=list)

    recommendations: List[str] = Field(default_factory=list)


# =============================================================================
# Test Generation Models
# =============================================================================

class GeneratedTest(BaseModel):
    """A generated verification test."""

    name: str
    code: str
    purpose: str

    expected_to_pass: bool = True

    # Metadata
    generated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    based_on_fix: Optional[str] = None


class TestCodeResponse(BaseModel):
    """Response from LLM test generation."""

    code: str
    explanation: str
    assertions: List[str] = Field(default_factory=list)


# =============================================================================
# Learning Models
# =============================================================================

class FixOutcome(BaseModel):
    """Outcome of a fix attempt."""

    status: FixOutcomeStatus
    modifications: Optional[str] = None
    reason: Optional[str] = None
    comment: Optional[str] = None
    decision_time_seconds: Optional[float] = None


class PatternAnalysis(BaseModel):
    """Analysis of fix patterns."""

    error_types: Dict[str, int]
    categories: Dict[str, int]
    strategies: Dict[str, int]

    common_patterns: List[str]
    failure_patterns: List[str]


class PromptImprovements(BaseModel):
    """Improvements to prompts from learning."""

    better_examples: List[Dict[str, Any]]
    things_to_avoid: List[Dict[str, Any]]
    category_hints: Dict[str, str]
    framework_hints: Dict[str, str]


class OptimizedPrompts(BaseModel):
    """Optimized prompts from continuous learning."""

    few_shot_examples: List[Dict[str, Any]]
    negative_examples: List[Dict[str, Any]]
    category_specific_guidance: Dict[str, str]
    framework_specific_hints: Dict[str, str]

    version: int = 1
    success_rate: Optional[float] = None


# =============================================================================
# Cross-Org Models
# =============================================================================

class AggregatedPattern(BaseModel):
    """Aggregated pattern across organizations."""

    error_type: str
    category: FlakinessCategory

    success_rate: float
    common_strategies: List[str]
    avg_fix_confidence: float
    sample_count: int


class CrossOrgHints(BaseModel):
    """Hints from cross-organization data."""

    available: bool

    success_rate: Optional[float] = None
    recommended_strategies: List[str] = Field(default_factory=list)
    avg_confidence: Optional[float] = None
    hint: Optional[str] = None


class PrivateEmbedding(BaseModel):
    """Privacy-preserving embedding."""

    embedding: List[float]
    features: Dict[str, Any]
    # No actual code stored


# =============================================================================
# Agentic Models
# =============================================================================

class AgentAction(BaseModel):
    """An action decided by the autonomous agent."""

    type: str  # complete, give_up, tool
    tool: Optional[str] = None
    parameters: Optional[Dict[str, Any]] = None
    reasoning: str


class ActionDecision(BaseModel):
    """Decision from LLM about next action."""

    action_type: Literal["tool", "complete", "give_up"]
    tool_name: Optional[str] = None
    parameters: Optional[Dict[str, Any]] = None
    reasoning: str


class DebugResult(BaseModel):
    """Result from autonomous debugging."""

    success: bool

    fix: Optional[FixHypothesis] = None
    reason: Optional[str] = None

    reasoning_chain: List[str] = Field(default_factory=list)
    steps_taken: int = 0
    tokens_used: int = 0


# =============================================================================
# Streaming Models
# =============================================================================

class FlakySignal(BaseModel):
    """Signal of potential flakiness during streaming."""

    reason: str
    confidence: float
    event_index: Optional[int] = None


class StreamingInsight(BaseModel):
    """Insight from streaming analysis."""

    type: str
    message: str
    confidence: float
    event_index: Optional[int] = None

    details: Optional[Dict[str, Any]] = None


# =============================================================================
# Tree of Thought Models
# =============================================================================

@dataclass
class ThoughtNode:
    """A node in the Tree of Thought exploration."""

    thought: str
    context: Optional[FailureContext] = None
    depth: int = 0

    evidence_needed: Optional[str] = None
    implication: Optional[str] = None

    parent: Optional[ThoughtNode] = None
    children: List[ThoughtNode] = field(default_factory=list)

    score: float = 0.0

    def get_path(self) -> List[ThoughtNode]:
        """Get path from root to this node."""
        path = []
        seen: set[int] = set()
        node = self
        while node is not None:
            node_id = id(node)
            if node_id in seen:
                break
            seen.add(node_id)
            path.append(node)
            node = node.parent
        return list(reversed(path))


class ThoughtExpansion(BaseModel):
    """Response from LLM thought expansion."""

    thoughts: List[Dict[str, str]]  # thought, evidence_needed, implication


class LocationScores(BaseModel):
    """Response from LLM location scoring."""

    scores: Dict[str, float]
    reasoning: Dict[str, str] = Field(default_factory=dict)


class FixResponse(BaseModel):
    """Response from LLM fix generation."""

    diff: str = Field(min_length=1)
    description: str = Field(min_length=1)
    confidence: float = Field(ge=0.0, le=1.0)
    reasoning: Optional[str] = None


class ExplanationResponse(BaseModel):
    """Response from LLM explanation generation."""

    explanation: str
