"""Configuration management for deliberate."""

from pathlib import Path
from typing import Literal

import platformdirs
import yaml
from pydantic import BaseModel, Field


class AgentCostConfig(BaseModel):
    """Cost configuration for an agent."""

    weight: float = 1.0
    tokens_per_dollar: int = 5000


class AgentRuntimeConfig(BaseModel):
    """Runtime configuration for an agent."""

    max_tokens: int = 8000
    timeout_seconds: int = 300


class AgentConfig(BaseModel):
    """Configuration for a single agent."""

    type: Literal["mcp", "cli", "api", "fake"]
    command: list[str] = Field(default_factory=list)
    env: dict[str, str] = Field(default_factory=dict)  # Environment variables for CLI agents
    mcp_endpoint: str | None = None
    model: str | None = None  # For API type agents
    capabilities: list[str] = Field(default_factory=lambda: ["planner", "executor", "reviewer"])
    behavior: str | None = None  # For fake agents: echo, planner, critic, flaky
    config: AgentRuntimeConfig = Field(default_factory=AgentRuntimeConfig)
    cost: AgentCostConfig = Field(default_factory=AgentCostConfig)


class MCPConfig(BaseModel):
    """MCP server configuration."""

    endpoint: str = "unix:///tmp/mcp-server.sock"
    tools: list[str] = Field(default_factory=lambda: ["fs", "git", "bash", "run_tests"])


class DebateConfig(BaseModel):
    """Debate configuration for planning phase."""

    enabled: bool = False
    rounds: int = 1
    max_messages_per_agent: int = 3
    turn_timeout_seconds: int = 60


class SelectionConfig(BaseModel):
    """Plan selection configuration."""

    method: Literal["llm_judge", "borda", "first"] = "first"
    judge: str | None = None


class PlanningConfig(BaseModel):
    """Planning phase configuration."""

    enabled: bool = True
    agents: list[str] = Field(default_factory=list)
    debate: DebateConfig = Field(default_factory=DebateConfig)
    selection: SelectionConfig = Field(default_factory=SelectionConfig)


class ParallelismConfig(BaseModel):
    """Execution parallelism configuration."""

    enabled: bool = False
    max_parallel: int = 1


class WorktreeConfig(BaseModel):
    """Worktree configuration for execution."""

    enabled: bool = True
    root: str = ".deliberate-worktrees"
    cleanup: bool = True


class QuestionsConfig(BaseModel):
    """Configuration for handling agent questions during execution."""

    strategy: Literal["prompt_user", "auto_answer", "fail"] = "fail"
    auto_answer_agent: str | None = None
    max_questions: int = 5


class ExecutionConfig(BaseModel):
    """Execution phase configuration."""

    enabled: bool = True
    agents: list[str] = Field(default_factory=list)
    parallelism: ParallelismConfig = Field(default_factory=ParallelismConfig)
    worktree: WorktreeConfig = Field(default_factory=WorktreeConfig)
    questions: QuestionsConfig = Field(default_factory=QuestionsConfig)


class ScoringConfig(BaseModel):
    """Review scoring configuration."""

    criteria: list[str] = Field(
        default_factory=lambda: ["correctness", "code_quality", "completeness", "risk"]
    )
    scale: str = "1-10"


class AggregationConfig(BaseModel):
    """Vote aggregation configuration."""

    method: Literal["borda", "approval", "weighted_borda"] = "borda"
    min_approval_ratio: float = 0.7


class SynthesisConfig(BaseModel):
    """Synthesis configuration for review phase."""

    enabled: bool = False
    rounds: int = 1
    synthesizers: list[str] = Field(default_factory=list)


class ReviewConfig(BaseModel):
    """Review phase configuration."""

    enabled: bool = True
    agents: list[str] = Field(default_factory=list)
    scoring: ScoringConfig = Field(default_factory=ScoringConfig)
    aggregation: AggregationConfig = Field(default_factory=AggregationConfig)
    synthesis: SynthesisConfig = Field(default_factory=SynthesisConfig)


class RefinementConfig(BaseModel):
    """Configuration for the refinement phase."""

    enabled: bool = False  # Disabled by default for backward compatibility

    # Trigger conditions (any condition met triggers refinement)
    min_confidence: float = 0.3  # Trigger if confidence < threshold
    min_winner_score: float = 0.6  # Trigger if winner score < threshold
    trigger_on_revise: bool = True  # Trigger if any reviewer recommends "revise"

    # Iteration controls
    max_iterations: int = 2
    min_improvement_threshold: float = 0.05  # Stop if improvement < 5%

    # Candidate selection
    refine_top_n: int = 1  # How many candidates to refine (1 = winner only)

    # Re-review strategy
    rereview_all: bool = False  # If False, only low-scoring reviewers re-review
    rereview_score_threshold: float = 0.5  # Re-review if original score < threshold

    # Budget management
    budget_reserve_pct: float = 0.3  # Reserve 30% of total budget for refinement
    per_iteration_budget_pct: float = 0.5  # Each iteration can use 50% of reserve

    # Behavior on regression
    revert_on_regression: bool = True  # Revert to previous iteration if scores decrease
    allow_score_decrease: float = 0.1  # Allow up to 10% score decrease before reverting


class WorkflowConfig(BaseModel):
    """Workflow configuration."""

    planning: PlanningConfig = Field(default_factory=PlanningConfig)
    execution: ExecutionConfig = Field(default_factory=ExecutionConfig)
    review: ReviewConfig = Field(default_factory=ReviewConfig)
    refinement: RefinementConfig = Field(default_factory=RefinementConfig)


class BudgetConfig(BaseModel):
    """Budget limits configuration."""

    max_total_tokens: int = 500000
    max_cost_usd: float = 10.0
    max_requests_per_agent: int = 30


class TimeConfig(BaseModel):
    """Time limits configuration."""

    hard_timeout_minutes: int = 45
    phase_timeouts: dict[str, int] = Field(
        default_factory=lambda: {"planning": 10, "execution": 25, "review": 10}
    )


class SafetyConfig(BaseModel):
    """Safety configuration."""

    require_human_approval: bool = False
    dry_run: bool = False


class LimitsConfig(BaseModel):
    """Resource limits configuration."""

    budget: BudgetConfig = Field(default_factory=BudgetConfig)
    time: TimeConfig = Field(default_factory=TimeConfig)
    safety: SafetyConfig = Field(default_factory=SafetyConfig)


class CIOutputConfig(BaseModel):
    """CI output configuration."""

    format: Literal["json", "markdown", "patch"] = "json"
    path: str = "deliberate-result.json"


class CIConfig(BaseModel):
    """CI mode configuration."""

    enabled: bool = False
    mode: Literal["suggest_only", "auto_apply"] = "suggest_only"
    output: CIOutputConfig = Field(default_factory=CIOutputConfig)
    fail_on: list[str] = Field(
        default_factory=lambda: ["test_failure", "low_confidence", "budget_exceeded"]
    )


class DeliberateConfig(BaseModel):
    """Root configuration for deliberate."""

    agents: dict[str, AgentConfig] = Field(default_factory=dict)
    mcp: MCPConfig = Field(default_factory=MCPConfig)
    workflow: WorkflowConfig = Field(default_factory=WorkflowConfig)
    limits: LimitsConfig = Field(default_factory=LimitsConfig)
    ci: CIConfig = Field(default_factory=CIConfig)

    @classmethod
    def load(cls, path: str | Path) -> "DeliberateConfig":
        """Load configuration from a YAML file."""
        path = Path(path)
        if not path.exists():
            raise FileNotFoundError(f"Configuration file not found: {path}")

        data = yaml.safe_load(path.read_text())
        return cls.model_validate(data)

    @classmethod
    def get_user_config_dir(cls) -> Path:
        """Get the OS-specific user configuration directory for deliberate."""
        return Path(platformdirs.user_config_dir("deliberate", appauthor=False))

    @classmethod
    def get_config_search_paths(cls) -> list[Path]:
        """Get list of paths to search for configuration files, in priority order.

        Priority:
        1. Current directory .deliberate.yaml
        2. Current directory deliberate.yaml
        3. User config directory ~/.deliberate/config.yaml (OS-specific)
        """
        user_config_dir = cls.get_user_config_dir()
        return [
            Path(".deliberate.yaml"),
            Path("deliberate.yaml"),
            user_config_dir / "config.yaml",
        ]

    @classmethod
    def load_or_default(cls, path: str | Path | None = None) -> "DeliberateConfig":
        """Load configuration from file or return default config.

        If path is None, searches in order:
        1. ./.deliberate.yaml (current directory)
        2. ./deliberate.yaml (current directory)
        3. ~/.deliberate/config.yaml (OS-specific user config directory)

        If no config file is found, returns default configuration.

        Args:
            path: Optional explicit path to configuration file

        Returns:
            Loaded configuration or default config
        """
        if path is not None:
            # Explicit path provided - check if it exists
            path = Path(path)
            if path.exists():
                return cls.load(path)
            # Explicit path doesn't exist, return default
            return cls()

        # Search for config files in priority order
        for candidate in cls.get_config_search_paths():
            if candidate.exists():
                return cls.load(candidate)

        # Return default configuration
        return cls()

    def get_agent(self, name: str) -> AgentConfig:
        """Get agent configuration by name."""
        if name not in self.agents:
            raise KeyError(f"Agent not found: {name}")
        return self.agents[name]

    def get_planners(self) -> list[str]:
        """Get list of agents with planner capability."""
        return [
            name
            for name, cfg in self.agents.items()
            if "planner" in cfg.capabilities and name in self.workflow.planning.agents
        ]

    def get_executors(self) -> list[str]:
        """Get list of agents with executor capability."""
        return [
            name
            for name, cfg in self.agents.items()
            if "executor" in cfg.capabilities and name in self.workflow.execution.agents
        ]

    def get_reviewers(self) -> list[str]:
        """Get list of agents with reviewer capability."""
        return [
            name
            for name, cfg in self.agents.items()
            if "reviewer" in cfg.capabilities and name in self.workflow.review.agents
        ]
