"""
Config Registry Service
=======================
Версійоване сховище конфігурацій для DAARION platform.

Зберігає:
- Системні промпти (версійовані)
- Routing rules
- Agent configurations
- Feature flags
- Quotas per user/team/agent
"""

import json
import hashlib
import logging
from datetime import datetime
from typing import Dict, Any, Optional, List
from dataclasses import dataclass, field
from pathlib import Path
import yaml

logger = logging.getLogger(__name__)

@dataclass
class ConfigVersion:
    """Версія конфігурації"""
    version: str
    content: Dict[str, Any]
    content_hash: str
    created_at: datetime
    created_by: str
    is_active: bool = False
    description: str = ""

@dataclass
class PromptConfig:
    """Конфігурація системного промпту"""
    agent_id: str
    current_version: str
    versions: Dict[str, ConfigVersion] = field(default_factory=dict)
    
    def get_active_prompt(self) -> str:
        """Повертає активний промпт"""
        if self.current_version in self.versions:
            return self.versions[self.current_version].content.get("prompt", "")
        return ""

@dataclass  
class RoutingRule:
    """Правило маршрутизації"""
    rule_id: str
    name: str
    conditions: Dict[str, Any]  # {"intent": "energy", "confidence_min": 0.7}
    target_agent: str
    priority: int = 0
    enabled: bool = True

@dataclass
class AgentQuota:
    """Квота для агента/користувача"""
    entity_type: str  # "user", "team", "agent"
    entity_id: str
    quota_type: str   # "tokens", "requests", "storage"
    limit: int
    used: int = 0
    reset_period: str = "daily"  # "daily", "weekly", "monthly"
    last_reset: Optional[datetime] = None


class ConfigRegistry:
    """
    Централізований реєстр конфігурацій.
    
    Підтримує:
    - Версіонування промптів
    - Routing rules
    - Feature flags
    - Quotas
    """
    
    def __init__(self, config_dir: str = "/opt/microdao-daarion/config"):
        self.config_dir = Path(config_dir)
        self.config_dir.mkdir(parents=True, exist_ok=True)
        
        # In-memory cache
        self._prompts: Dict[str, PromptConfig] = {}
        self._routing_rules: Dict[str, RoutingRule] = {}
        self._feature_flags: Dict[str, bool] = {}
        self._quotas: Dict[str, AgentQuota] = {}
        
        # Load existing configs
        self._load_all()
    
    def _load_all(self):
        """Завантажує всі конфігурації з файлів"""
        self._load_prompts()
        self._load_routing_rules()
        self._load_feature_flags()
        self._load_quotas()
    
    def _compute_hash(self, content: Any) -> str:
        """Обчислює SHA256 хеш контенту"""
        content_str = json.dumps(content, sort_keys=True, default=str)
        return hashlib.sha256(content_str.encode()).hexdigest()[:16]
    
    # ==================== PROMPTS ====================
    
    def _load_prompts(self):
        """Завантажує промпти з файлів"""
        prompts_dir = self.config_dir / "prompts"
        if not prompts_dir.exists():
            prompts_dir.mkdir(parents=True, exist_ok=True)
            return
        
        for prompt_file in prompts_dir.glob("*.yaml"):
            try:
                with open(prompt_file, 'r', encoding='utf-8') as f:
                    data = yaml.safe_load(f)
                    if data:
                        agent_id = prompt_file.stem
                        self._prompts[agent_id] = self._parse_prompt_config(agent_id, data)
            except Exception as e:
                logger.error(f"Failed to load prompt {prompt_file}: {e}")
    
    def _parse_prompt_config(self, agent_id: str, data: Dict) -> PromptConfig:
        """Парсить конфігурацію промпту"""
        versions = {}
        for ver_id, ver_data in data.get("versions", {}).items():
            versions[ver_id] = ConfigVersion(
                version=ver_id,
                content=ver_data.get("content", {}),
                content_hash=self._compute_hash(ver_data.get("content", {})),
                created_at=datetime.fromisoformat(ver_data.get("created_at", datetime.now().isoformat())),
                created_by=ver_data.get("created_by", "system"),
                is_active=ver_data.get("is_active", False),
                description=ver_data.get("description", "")
            )
        
        return PromptConfig(
            agent_id=agent_id,
            current_version=data.get("current_version", "v1"),
            versions=versions
        )
    
    def get_prompt(self, agent_id: str, version: str = None) -> Optional[str]:
        """
        Повертає промпт для агента.
        
        Args:
            agent_id: ID агента
            version: Версія (або None для активної)
        """
        if agent_id not in self._prompts:
            return None
        
        config = self._prompts[agent_id]
        target_version = version or config.current_version
        
        if target_version in config.versions:
            return config.versions[target_version].content.get("prompt", "")
        
        return None
    
    def set_prompt(self, agent_id: str, prompt: str, 
                   version: str = None, created_by: str = "system",
                   description: str = "", activate: bool = True) -> str:
        """
        Створює нову версію промпту.
        
        Returns:
            Версія нового промпту
        """
        if agent_id not in self._prompts:
            self._prompts[agent_id] = PromptConfig(
                agent_id=agent_id,
                current_version="v1",
                versions={}
            )
        
        config = self._prompts[agent_id]
        
        # Generate version if not provided
        if version is None:
            version = f"v{len(config.versions) + 1}"
        
        # Create new version
        content = {"prompt": prompt}
        config.versions[version] = ConfigVersion(
            version=version,
            content=content,
            content_hash=self._compute_hash(content),
            created_at=datetime.now(),
            created_by=created_by,
            is_active=activate,
            description=description
        )
        
        if activate:
            # Deactivate previous
            for v in config.versions.values():
                if v.version != version:
                    v.is_active = False
            config.current_version = version
        
        # Save to file
        self._save_prompt(agent_id)
        
        logger.info(f"Created prompt version {version} for {agent_id}")
        return version
    
    def _save_prompt(self, agent_id: str):
        """Зберігає промпт у файл"""
        prompts_dir = self.config_dir / "prompts"
        prompts_dir.mkdir(parents=True, exist_ok=True)
        
        config = self._prompts[agent_id]
        data = {
            "agent_id": agent_id,
            "current_version": config.current_version,
            "versions": {
                v.version: {
                    "content": v.content,
                    "content_hash": v.content_hash,
                    "created_at": v.created_at.isoformat(),
                    "created_by": v.created_by,
                    "is_active": v.is_active,
                    "description": v.description
                }
                for v in config.versions.values()
            }
        }
        
        with open(prompts_dir / f"{agent_id}.yaml", 'w', encoding='utf-8') as f:
            yaml.dump(data, f, allow_unicode=True, default_flow_style=False)
    
    def list_prompt_versions(self, agent_id: str) -> List[Dict]:
        """Повертає список версій промпту"""
        if agent_id not in self._prompts:
            return []
        
        return [
            {
                "version": v.version,
                "content_hash": v.content_hash,
                "created_at": v.created_at.isoformat(),
                "created_by": v.created_by,
                "is_active": v.is_active,
                "description": v.description
            }
            for v in self._prompts[agent_id].versions.values()
        ]
    
    def rollback_prompt(self, agent_id: str, version: str) -> bool:
        """Відкатує промпт до вказаної версії"""
        if agent_id not in self._prompts:
            return False
        
        config = self._prompts[agent_id]
        if version not in config.versions:
            return False
        
        # Deactivate all
        for v in config.versions.values():
            v.is_active = False
        
        # Activate target
        config.versions[version].is_active = True
        config.current_version = version
        
        self._save_prompt(agent_id)
        logger.info(f"Rolled back {agent_id} prompt to {version}")
        return True
    
    # ==================== ROUTING RULES ====================
    
    def _load_routing_rules(self):
        """Завантажує routing rules"""
        rules_file = self.config_dir / "routing_rules.yaml"
        if not rules_file.exists():
            return
        
        try:
            with open(rules_file, 'r', encoding='utf-8') as f:
                data = yaml.safe_load(f) or {}
                for rule_id, rule_data in data.get("rules", {}).items():
                    self._routing_rules[rule_id] = RoutingRule(
                        rule_id=rule_id,
                        name=rule_data.get("name", rule_id),
                        conditions=rule_data.get("conditions", {}),
                        target_agent=rule_data.get("target_agent", ""),
                        priority=rule_data.get("priority", 0),
                        enabled=rule_data.get("enabled", True)
                    )
        except Exception as e:
            logger.error(f"Failed to load routing rules: {e}")
    
    def get_routing_rules(self, enabled_only: bool = True) -> List[RoutingRule]:
        """Повертає routing rules, відсортовані за пріоритетом"""
        rules = list(self._routing_rules.values())
        if enabled_only:
            rules = [r for r in rules if r.enabled]
        return sorted(rules, key=lambda r: r.priority, reverse=True)
    
    def add_routing_rule(self, rule: RoutingRule):
        """Додає routing rule"""
        self._routing_rules[rule.rule_id] = rule
        self._save_routing_rules()
    
    def _save_routing_rules(self):
        """Зберігає routing rules"""
        data = {
            "rules": {
                r.rule_id: {
                    "name": r.name,
                    "conditions": r.conditions,
                    "target_agent": r.target_agent,
                    "priority": r.priority,
                    "enabled": r.enabled
                }
                for r in self._routing_rules.values()
            }
        }
        
        with open(self.config_dir / "routing_rules.yaml", 'w', encoding='utf-8') as f:
            yaml.dump(data, f, allow_unicode=True, default_flow_style=False)
    
    # ==================== FEATURE FLAGS ====================
    
    def _load_feature_flags(self):
        """Завантажує feature flags"""
        flags_file = self.config_dir / "feature_flags.yaml"
        if not flags_file.exists():
            # Default flags
            self._feature_flags = {
                "orchestration_enabled": True,
                "soft_handoff_enabled": True,
                "auto_handoff_enabled": False,
                "privacy_gate_enabled": True,
                "audit_logging_enabled": True,
                "multi_agent_workflows": False,
            }
            self._save_feature_flags()
            return
        
        try:
            with open(flags_file, 'r', encoding='utf-8') as f:
                self._feature_flags = yaml.safe_load(f) or {}
        except Exception as e:
            logger.error(f"Failed to load feature flags: {e}")
    
    def get_feature_flag(self, flag_name: str, default: bool = False) -> bool:
        """Повертає значення feature flag"""
        return self._feature_flags.get(flag_name, default)
    
    def set_feature_flag(self, flag_name: str, value: bool):
        """Встановлює feature flag"""
        self._feature_flags[flag_name] = value
        self._save_feature_flags()
        logger.info(f"Feature flag {flag_name} set to {value}")
    
    def _save_feature_flags(self):
        """Зберігає feature flags"""
        with open(self.config_dir / "feature_flags.yaml", 'w', encoding='utf-8') as f:
            yaml.dump(self._feature_flags, f, allow_unicode=True)
    
    # ==================== QUOTAS ====================
    
    def _load_quotas(self):
        """Завантажує квоти"""
        quotas_file = self.config_dir / "quotas.yaml"
        if not quotas_file.exists():
            return
        
        try:
            with open(quotas_file, 'r', encoding='utf-8') as f:
                data = yaml.safe_load(f) or {}
                for quota_id, quota_data in data.get("quotas", {}).items():
                    self._quotas[quota_id] = AgentQuota(
                        entity_type=quota_data.get("entity_type", "user"),
                        entity_id=quota_data.get("entity_id", ""),
                        quota_type=quota_data.get("quota_type", "requests"),
                        limit=quota_data.get("limit", 1000),
                        used=quota_data.get("used", 0),
                        reset_period=quota_data.get("reset_period", "daily"),
                        last_reset=datetime.fromisoformat(quota_data["last_reset"]) if quota_data.get("last_reset") else None
                    )
        except Exception as e:
            logger.error(f"Failed to load quotas: {e}")
    
    def get_quota(self, entity_type: str, entity_id: str, quota_type: str) -> Optional[AgentQuota]:
        """Повертає квоту"""
        quota_id = f"{entity_type}:{entity_id}:{quota_type}"
        return self._quotas.get(quota_id)
    
    def check_quota(self, entity_type: str, entity_id: str, 
                    quota_type: str, amount: int = 1) -> bool:
        """
        Перевіряє, чи є достатньо квоти.
        
        Returns:
            True якщо квота дозволяє, False якщо ліміт перевищено
        """
        quota = self.get_quota(entity_type, entity_id, quota_type)
        if quota is None:
            return True  # No quota = unlimited
        
        return (quota.used + amount) <= quota.limit
    
    def consume_quota(self, entity_type: str, entity_id: str,
                      quota_type: str, amount: int = 1) -> bool:
        """
        Споживає квоту.
        
        Returns:
            True якщо успішно, False якщо ліміт перевищено
        """
        quota_id = f"{entity_type}:{entity_id}:{quota_type}"
        
        if quota_id not in self._quotas:
            # Create default quota
            self._quotas[quota_id] = AgentQuota(
                entity_type=entity_type,
                entity_id=entity_id,
                quota_type=quota_type,
                limit=10000,  # Default limit
                used=0,
                reset_period="daily"
            )
        
        quota = self._quotas[quota_id]
        
        if (quota.used + amount) > quota.limit:
            return False
        
        quota.used += amount
        self._save_quotas()
        return True
    
    def _save_quotas(self):
        """Зберігає квоти"""
        data = {
            "quotas": {
                f"{q.entity_type}:{q.entity_id}:{q.quota_type}": {
                    "entity_type": q.entity_type,
                    "entity_id": q.entity_id,
                    "quota_type": q.quota_type,
                    "limit": q.limit,
                    "used": q.used,
                    "reset_period": q.reset_period,
                    "last_reset": q.last_reset.isoformat() if q.last_reset else None
                }
                for q in self._quotas.values()
            }
        }
        
        with open(self.config_dir / "quotas.yaml", 'w', encoding='utf-8') as f:
            yaml.dump(data, f, allow_unicode=True, default_flow_style=False)


# Global instance
_config_registry: Optional[ConfigRegistry] = None

def get_config_registry() -> ConfigRegistry:
    """Повертає глобальний екземпляр ConfigRegistry"""
    global _config_registry
    if _config_registry is None:
        _config_registry = ConfigRegistry()
    return _config_registry


# ==================== HTTP API ====================

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel

router = APIRouter(prefix="/config", tags=["config"])

class PromptRequest(BaseModel):
    prompt: str
    version: Optional[str] = None
    description: str = ""
    activate: bool = True

class FeatureFlagRequest(BaseModel):
    value: bool

@router.get("/prompts/{agent_id}")
async def get_prompt(agent_id: str, version: str = None):
    """Отримати промпт для агента"""
    registry = get_config_registry()
    prompt = registry.get_prompt(agent_id, version)
    if prompt is None:
        raise HTTPException(404, f"Prompt not found for {agent_id}")
    return {"agent_id": agent_id, "prompt": prompt}

@router.get("/prompts/{agent_id}/versions")
async def list_prompt_versions(agent_id: str):
    """Список версій промпту"""
    registry = get_config_registry()
    versions = registry.list_prompt_versions(agent_id)
    return {"agent_id": agent_id, "versions": versions}

@router.post("/prompts/{agent_id}")
async def set_prompt(agent_id: str, request: PromptRequest):
    """Створити нову версію промпту"""
    registry = get_config_registry()
    version = registry.set_prompt(
        agent_id=agent_id,
        prompt=request.prompt,
        version=request.version,
        description=request.description,
        activate=request.activate
    )
    return {"agent_id": agent_id, "version": version, "activated": request.activate}

@router.post("/prompts/{agent_id}/rollback/{version}")
async def rollback_prompt(agent_id: str, version: str):
    """Відкатити промпт до версії"""
    registry = get_config_registry()
    success = registry.rollback_prompt(agent_id, version)
    if not success:
        raise HTTPException(404, f"Version {version} not found")
    return {"agent_id": agent_id, "version": version, "status": "rolled_back"}

@router.get("/feature-flags")
async def get_feature_flags():
    """Отримати всі feature flags"""
    registry = get_config_registry()
    return {"flags": registry._feature_flags}

@router.get("/feature-flags/{flag_name}")
async def get_feature_flag(flag_name: str):
    """Отримати feature flag"""
    registry = get_config_registry()
    value = registry.get_feature_flag(flag_name)
    return {"flag": flag_name, "value": value}

@router.put("/feature-flags/{flag_name}")
async def set_feature_flag(flag_name: str, request: FeatureFlagRequest):
    """Встановити feature flag"""
    registry = get_config_registry()
    registry.set_feature_flag(flag_name, request.value)
    return {"flag": flag_name, "value": request.value}

@router.get("/routing-rules")
async def get_routing_rules(enabled_only: bool = True):
    """Отримати routing rules"""
    registry = get_config_registry()
    rules = registry.get_routing_rules(enabled_only)
    return {"rules": [
        {
            "rule_id": r.rule_id,
            "name": r.name,
            "conditions": r.conditions,
            "target_agent": r.target_agent,
            "priority": r.priority,
            "enabled": r.enabled
        }
        for r in rules
    ]}

@router.get("/health")
async def health():
    """Health check"""
    return {"status": "ok", "service": "config-registry"}
