"""Unified configuration via pydantic-settings."""

import json
from pathlib import Path
from typing import Any, Literal

from pydantic import field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict


def build_directory_names(directories: list[Path]) -> dict[str, Path]:
    """Map short names to paths using basenames, disambiguating conflicts with parent."""
    if not directories:
        return {}

    basenames: dict[str, list[Path]] = {}
    for d in directories:
        basenames.setdefault(d.name, []).append(d)

    names: dict[str, Path] = {}
    for basename, paths in basenames.items():
        if len(paths) == 1:
            names[basename] = paths[0]
        else:
            for p in paths:
                qualified = f"{p.parent.name}/{p.name}"
                names[qualified] = p
    return names


_LEASHD_GITIGNORE = """\
# Auto-generated by leashd — ignore runtime artifacts, keep config
*
!.gitignore
!test.yaml
!test.yml
!workflows/
!workflows/*.yaml
!workflows/*.yml
"""


def ensure_leashd_dir(base: Path) -> Path:
    """Create `base/.leashd/` with a `.gitignore` that tracks only config files."""
    leashd_dir = base / ".leashd"
    leashd_dir.mkdir(parents=True, exist_ok=True)
    gitignore = leashd_dir / ".gitignore"
    if not gitignore.exists():
        gitignore.write_text(_LEASHD_GITIGNORE)
    return leashd_dir


class LeashdConfig(BaseSettings):
    model_config = SettingsConfigDict(
        env_prefix="LEASHD_",
        env_file=".env",
        env_file_encoding="utf-8",
        extra="ignore",
    )

    # Required
    approved_directories: list[Path]

    # Agent settings
    agent_runtime: str = "claude-cli"
    max_turns: int = 250
    web_max_turns: int = 300
    test_max_turns: int = 200
    max_concurrent_agents: int = 5
    agent_timeout_seconds: int = 3600  # 60 minutes
    system_prompt: str | None = None
    allowed_tools: list[str] = []
    disallowed_tools: list[str] = []
    mcp_servers: dict[str, Any] = {}
    effort: Literal["low", "medium", "high", "max"] | None = "medium"

    # Codex agent settings (only apply when agent_runtime="codex")
    codex_model: str | None = None
    codex_sandbox: str | None = None
    codex_approval: str | None = None
    codex_search: bool = False

    # Safety settings
    policy_files: list[Path] = []
    approval_timeout_seconds: int = 300
    interaction_timeout_seconds: int | None = None

    # Auth & rate limiting
    allowed_user_ids: set[str] = set()
    rate_limit_rpm: int = 0
    rate_limit_burst: int = 5

    # Connector
    telegram_bot_token: str | None = None

    # WebUI
    web_enabled: bool = False
    web_host: str = "0.0.0.0"  # noqa: S104
    web_port: int = 8080
    web_api_key: str | None = None
    web_cors_origins: str = ""
    web_dev_mode: bool = False
    web_telegram_notify: bool = False

    # Workspaces
    workspace_config_root: Path | None = None

    # Storage
    storage_backend: str = "sqlite"
    storage_path: Path = Path(".leashd/messages.db")

    # Agent mode
    default_mode: Literal["default", "plan", "auto"] = "default"

    # Browser
    browser_backend: Literal["playwright", "agent-browser"] = "playwright"
    browser_user_data_dir: str | None = None
    browser_headless: bool = False

    # Autonomous mode
    auto_approver: bool = False
    auto_approver_model: str | None = None
    auto_approver_max_calls: int = 50
    autonomous_loop: bool = False
    autonomous_max_retries: int = 3
    auto_plan: bool = False
    auto_plan_model: str | None = None
    auto_pr: bool = False
    auto_pr_base_branch: str = "main"

    # Task orchestration
    task_orchestrator: bool = False
    task_max_retries: int = 3
    task_phase_timeout_seconds: int = 1800  # 30 minutes per phase

    # Streaming
    streaming_enabled: bool = True
    streaming_throttle_seconds: float = 0.15

    # Logging
    log_level: str = "INFO"
    audit_log_path: Path = Path(".leashd/audit.jsonl")
    log_dir: Path | None = Path(".leashd/logs")
    log_max_bytes: int = 10_485_760
    log_backup_count: int = 5

    @field_validator("approved_directories", mode="before")
    @classmethod
    def parse_approved_directories(cls, v: list[Path] | str | Path) -> list[Path]:
        if isinstance(v, Path):
            return [v]
        if isinstance(v, str):
            return [Path(p.strip()) for p in v.split(",") if p.strip()]
        return v

    @field_validator("approved_directories")
    @classmethod
    def resolve_approved_directories(cls, v: list[Path]) -> list[Path]:
        if not v:
            raise ValueError("approved_directories must not be empty")
        resolved = []
        for p in v:
            r = p.expanduser().resolve()
            if not r.is_dir():
                raise ValueError(f"approved directory does not exist: {r}")
            resolved.append(r)
        return resolved

    @field_validator("allowed_user_ids", mode="before")
    @classmethod
    def parse_allowed_user_ids(cls, v: set[str] | str | int) -> set[str]:
        if isinstance(v, int):
            return {str(v)}
        if isinstance(v, str):
            return {s.strip() for s in v.split(",") if s.strip()}
        return v

    @field_validator("mcp_servers", mode="before")
    @classmethod
    def parse_mcp_servers(cls, v: dict[str, Any] | str) -> dict[str, Any]:
        if isinstance(v, str):
            result: dict[str, Any] = json.loads(v)
            return result
        return v

    @field_validator("policy_files", mode="before")
    @classmethod
    def parse_policy_files(cls, v: list[Path] | str) -> list[Path]:
        if isinstance(v, str):
            return [Path(p.strip()) for p in v.split(",") if p.strip()]
        return v

    def effective_max_turns(self, mode: str) -> int:
        """Return the turn limit for the given session mode."""
        limits = {"web": self.web_max_turns, "test": self.test_max_turns}
        return limits.get(mode, self.max_turns)
