#!/usr/bin/env python3
"""Agent config loader and validator.

Loads YAML config files from telegram/agents/*.yaml, validates required fields,
resolves file paths. Used by bot.py and future agent_runner.py.

Epimetheus owns this module.
"""

import logging
import os
import re
from dataclasses import dataclass, field
from typing import Optional
from urllib.parse import urlparse

logger = logging.getLogger("tg.agent_config")

SECRETS_DIR = "/opt/teleo-eval/secrets"
WORKTREE_DIR = "/opt/teleo-eval/workspaces/main"

REQUIRED_FIELDS = ["name", "handle", "bot_token_file", "pentagon_agent_id", "domain"]
REQUIRED_VOICE_FIELDS = ["voice_summary", "voice_definition"]
REQUIRED_KB_FIELDS = ["kb_scope"]


@dataclass
class AgentConfig:
    """Validated agent configuration loaded from YAML."""
    name: str
    handle: str
    x_handle: Optional[str]
    bot_token_file: str
    pentagon_agent_id: str
    domain: str
    kb_scope_primary: list[str]
    voice_summary: str
    voice_definition: str
    domain_expertise: str
    learnings_file: str
    opsec_additional_patterns: list[str] = field(default_factory=list)
    response_model: str = "anthropic/claude-opus-4-6"
    triage_model: str = "anthropic/claude-haiku-4.5"
    max_tokens: int = 1024
    max_response_per_user_per_hour: int = 30
    http_chat_proxy_url: Optional[str] = None
    http_research_proxy_url: Optional[str] = None
    respond_to_private_chats: bool = False
    mention_aliases: list[str] = field(default_factory=list)
    smart_research_command_prefixes: list[str] = field(default_factory=list)
    auto_smart_research_from_chat: bool = False

    def to_dict(self) -> dict:
        """Convert to dict for passing to build_system_prompt."""
        return {
            "name": self.name,
            "handle": self.handle,
            "x_handle": self.x_handle,
            "domain": self.domain,
            "voice_definition": self.voice_definition,
            "voice_summary": self.voice_summary,
            "domain_expertise": self.domain_expertise,
            "pentagon_agent_id": self.pentagon_agent_id,
            "http_chat_proxy_url": self.http_chat_proxy_url,
            "http_research_proxy_url": self.http_research_proxy_url,
            "respond_to_private_chats": self.respond_to_private_chats,
            "mention_aliases": self.mention_aliases,
            "smart_research_command_prefixes": self.smart_research_command_prefixes,
            "auto_smart_research_from_chat": self.auto_smart_research_from_chat,
        }

    @property
    def bot_token_path(self) -> str:
        return os.path.join(SECRETS_DIR, self.bot_token_file)

    @property
    def learnings_path(self) -> str:
        return os.path.join(WORKTREE_DIR, self.learnings_file)

    @property
    def handle_regex(self) -> re.Pattern:
        """Regex matching this agent's @handle with optional @botname suffix."""
        clean = self.handle.lstrip("@")
        return re.compile(rf"@{re.escape(clean)}(?:@\w+)?", re.IGNORECASE)


def load_agent_config(config_path: str) -> AgentConfig:
    """Load and validate an agent YAML config file.

    Raises ValueError on validation failure.
    """
    import yaml

    with open(config_path) as f:
        raw = yaml.safe_load(f)

    errors = []

    # Required fields
    for fld in REQUIRED_FIELDS + REQUIRED_VOICE_FIELDS:
        if fld not in raw or not raw[fld]:
            errors.append(f"Missing required field: {fld}")

    # KB scope
    kb_scope = raw.get("kb_scope", {})
    if not isinstance(kb_scope, dict) or "primary" not in kb_scope:
        errors.append("Missing kb_scope.primary (list of primary domain dirs)")
    elif not isinstance(kb_scope["primary"], list) or len(kb_scope["primary"]) == 0:
        errors.append("kb_scope.primary must be a non-empty list")

    # Learnings file
    if "learnings_file" not in raw:
        errors.append("Missing required field: learnings_file")

    proxy_url = raw.get("http_chat_proxy_url")
    if proxy_url:
        parsed_proxy = urlparse(proxy_url)
        if parsed_proxy.scheme not in {"http", "https"} or not parsed_proxy.netloc:
            errors.append("http_chat_proxy_url must be an absolute http(s) URL")

    research_proxy_url = raw.get("http_research_proxy_url")
    if research_proxy_url:
        parsed_research_proxy = urlparse(research_proxy_url)
        if parsed_research_proxy.scheme not in {"http", "https"} or not parsed_research_proxy.netloc:
            errors.append("http_research_proxy_url must be an absolute http(s) URL")

    mention_aliases = raw.get("mention_aliases", [])
    if mention_aliases and not isinstance(mention_aliases, list):
        errors.append("mention_aliases must be a list")

    smart_research_command_prefixes = raw.get("smart_research_command_prefixes", [])
    if smart_research_command_prefixes and not isinstance(smart_research_command_prefixes, list):
        errors.append("smart_research_command_prefixes must be a list")
    for prefix in smart_research_command_prefixes or []:
        if not isinstance(prefix, str) or not prefix.startswith("/"):
            errors.append("smart_research_command_prefixes entries must start with /")

    if errors:
        raise ValueError(
            f"Agent config validation failed ({config_path}):\n"
            + "\n".join(f"  - {e}" for e in errors)
        )

    return AgentConfig(
        name=raw["name"],
        handle=raw["handle"],
        x_handle=raw.get("x_handle"),
        bot_token_file=raw["bot_token_file"],
        pentagon_agent_id=raw["pentagon_agent_id"],
        domain=raw["domain"],
        kb_scope_primary=kb_scope["primary"],
        voice_summary=raw["voice_summary"],
        voice_definition=raw["voice_definition"],
        domain_expertise=raw.get("domain_expertise", ""),
        learnings_file=raw["learnings_file"],
        opsec_additional_patterns=raw.get("opsec_additional_patterns", []),
        response_model=raw.get("response_model", "anthropic/claude-opus-4-6"),
        triage_model=raw.get("triage_model", "anthropic/claude-haiku-4.5"),
        max_tokens=raw.get("max_tokens", 1024),
        max_response_per_user_per_hour=raw.get("max_response_per_user_per_hour", 30),
        http_chat_proxy_url=proxy_url,
        http_research_proxy_url=research_proxy_url,
        respond_to_private_chats=bool(raw.get("respond_to_private_chats", False)),
        mention_aliases=mention_aliases,
        smart_research_command_prefixes=smart_research_command_prefixes,
        auto_smart_research_from_chat=bool(raw.get("auto_smart_research_from_chat", False)),
    )


def validate_agent_config(config_path: str) -> list[str]:
    """Validate config file and check runtime dependencies.

    Returns list of warnings (empty = all good).
    Raises ValueError on hard failures.
    """
    config = load_agent_config(config_path)
    warnings = []

    # Check bot token file exists
    if not os.path.exists(config.bot_token_path):
        warnings.append(f"Bot token file not found: {config.bot_token_path}")

    # Check primary KB dirs exist
    for d in config.kb_scope_primary:
        full = os.path.join(WORKTREE_DIR, d)
        if not os.path.isdir(full):
            warnings.append(f"KB scope dir not found: {full}")

    # Check learnings file parent dir exists
    learnings_dir = os.path.dirname(config.learnings_path)
    if not os.path.isdir(learnings_dir):
        warnings.append(f"Learnings dir not found: {learnings_dir}")

    # Validate OPSEC patterns compile
    for i, pattern in enumerate(config.opsec_additional_patterns):
        try:
            re.compile(pattern, re.IGNORECASE)
        except re.error as e:
            warnings.append(f"Invalid OPSEC regex pattern [{i}]: {e}")

    return warnings
