from dataclasses import dataclass, asdict, field
from typing import Dict, List, Optional
import json
from pathlib import Path
import os
import logging

from armorer.config import resolve_armorer_config_dir

logger = logging.getLogger(__name__)

@dataclass
class AppMetadata:
    name: str
    install_path: str
    agent_id: str = ""
    installed_at: Optional[float] = None
    install_type: str = "docker-compose"
    version: str = "latest"
    description: str = ""
    run_preference: Optional[str] = None
    run_options_cache: Optional[List[dict]] = None
    
    # Enhanced Metadata
    config_files: Dict[str, str] = field(default_factory=dict) # e.g. {"env": ".env", "main": "config/app.json"}
    injected_keys: List[str] = field(default_factory=list) # Keys from Vault used here
    injected_skills: List[str] = field(default_factory=list) # Skills from Armory used here
    capabilities: List[str] = field(default_factory=list) # e.g. ["mcp", "docker"]
    monitor_targets: List[str] = field(default_factory=list) # Files/patterns to monitor (e.g. ["sessions/*.json"])
    monitoring_mode: str = "hybrid" # one of: file, otel, hybrid
    run_commands: List[Dict[str, str]] = field(default_factory=list) # e.g. [{"label": "Setup", "service": "cli", "command": "onboard"}]
    app_config_state: Dict[str, object] = field(default_factory=dict)
    managed_files: List[str] = field(default_factory=list)
    channels_enabled: List[str] = field(default_factory=list)
    skills_enabled: List[str] = field(default_factory=list)

class AppRegistry:
    """Manages the registry of installed applications."""
    
    REGISTRY_FILE = Path.home() / ".armorer" / "registry.json"

    def __init__(self):
        registry_file = Path(type(self).REGISTRY_FILE)
        self.REGISTRY_FILE = resolve_armorer_config_dir(registry_file.parent) / registry_file.name
        try:
            self.REGISTRY_FILE.parent.mkdir(parents=True, exist_ok=True)
        except OSError:
            pass
        self._ensure_registry()
        self._sync_known_monitor_targets()

    def _ensure_registry(self):
        if not self.REGISTRY_FILE.exists():
            self._safe_save_registry({})

    def _load_registry(self) -> Dict[str, dict]:
        try:
            with open(self.REGISTRY_FILE, "r") as f:
                return json.load(f)
        except (FileNotFoundError, json.JSONDecodeError, OSError):
            return {}

    def _save_registry(self, data: Dict[str, dict]):
        self.REGISTRY_FILE.parent.mkdir(parents=True, exist_ok=True)
        with open(self.REGISTRY_FILE, "w") as f:
            json.dump(data, f, indent=4)

    def _safe_save_registry(self, data: Dict[str, dict]) -> bool:
        try:
            self._save_registry(data)
            return True
        except OSError as exc:
            logger.debug("Best-effort registry save skipped for %s: %s", self.REGISTRY_FILE, exc)
            return False

    def _normalize_install_path(self, install_path: str) -> str:
        """Translate legacy/container install paths to current host paths when possible."""
        raw = str(install_path or "").strip()
        if not raw:
            return raw
        # If this already exists, keep as-is.
        if Path(raw).exists():
            return raw
        # Common migration: core stored /app/installed/<name> while CLI runs on host.
        if raw.startswith("/app/installed/"):
            rel = raw[len("/app/installed/") :]
            candidates = []
            host_root = os.getenv("HOST_PROJECT_ROOT", "").strip()
            if host_root:
                candidates.append(str((Path(host_root) / "installed" / rel).resolve()))
            candidates.append(str((Path.cwd() / "installed" / rel).resolve()))
            for candidate in candidates:
                if Path(candidate).exists():
                    return candidate
        return raw

    def _sync_known_monitor_targets(self):
        """
        Auto-merge playbook monitor targets into existing registry entries.
        This makes monitor target improvements apply to already-installed apps.
        """
        try:
            from armorer.catalog import resolve_playbook
        except Exception:
            return

        registry = self._load_registry()
        changed = False
        for app_name, data in list(registry.items()):
            if not isinstance(data, dict):
                continue
            if str(data.get("monitoring_mode") or "").strip().lower() not in {"file", "otel", "hybrid"}:
                data["monitoring_mode"] = "hybrid"
                registry[app_name] = data
                changed = True

            agent_id = str(data.get("agent_id") or "").strip()
            install_path = str(data.get("install_path") or "").strip()
            playbook = (
                resolve_playbook(agent_id)
                or resolve_playbook(app_name)
                or resolve_playbook(install_path)
            )
            if not playbook:
                continue

            pb_targets = [str(t) for t in (getattr(playbook, "monitor_targets", []) or []) if t]
            pb_mode = str(getattr(playbook, "monitoring_mode", "") or "").strip().lower()
            if not pb_targets:
                continue

            existing = [str(t) for t in (data.get("monitor_targets") or []) if t]
            merged: List[str] = []
            seen = set()
            for target in existing + pb_targets:
                if target in seen:
                    continue
                merged.append(target)
                seen.add(target)

            if merged != existing:
                data["monitor_targets"] = merged
                registry[app_name] = data
                changed = True
                try:
                    monitor_file = Path(install_path) / ".armorer_monitor.json"
                    monitor_file.parent.mkdir(parents=True, exist_ok=True)
                    monitor_file.write_text(json.dumps({"targets": merged}))
                except Exception:
                    pass
            if pb_mode in {"file", "otel", "hybrid"} and str(data.get("monitoring_mode") or "").strip().lower() != pb_mode:
                data["monitoring_mode"] = pb_mode
                registry[app_name] = data
                changed = True

        if changed:
            self._safe_save_registry(registry)

    def add_app(self, app: AppMetadata):
        """Register a new application."""
        registry = self._load_registry()
        registry[app.name] = asdict(app)
        self._save_registry(registry)

    def get_app(self, name: str) -> Optional[AppMetadata]:
        """Retrieve application metadata by name."""
        registry = self._load_registry()
        data = registry.get(name)
        if data:
            # Safe instantiation: ignore extra keys that might exist in the registry file
            import inspect
            valid_keys = inspect.signature(AppMetadata).parameters.keys()
            filtered_data = {k: v for k, v in data.items() if k in valid_keys}
            if "install_path" in filtered_data:
                normalized = self._normalize_install_path(str(filtered_data["install_path"]))
                if normalized != str(filtered_data["install_path"]):
                    filtered_data["install_path"] = normalized
                    data["install_path"] = normalized
                    registry[name] = data
                    self._safe_save_registry(registry)
            return AppMetadata(**filtered_data)
        return None

    def list_apps(self) -> List[AppMetadata]:
        """List all registered applications."""
        registry = self._load_registry()
        import inspect
        valid_keys = inspect.signature(AppMetadata).parameters.keys()
        
        apps = []
        changed = False
        for data in registry.values():
            filtered_data = {k: v for k, v in data.items() if k in valid_keys}
            if "install_path" in filtered_data:
                normalized = self._normalize_install_path(str(filtered_data["install_path"]))
                if normalized != str(filtered_data["install_path"]):
                    filtered_data["install_path"] = normalized
                    data["install_path"] = normalized
                    changed = True
            apps.append(AppMetadata(**filtered_data))
        if changed:
            self._safe_save_registry(registry)
        return apps

    def remove_app(self, name: str):
        """Remove an application from the registry."""
        registry = self._load_registry()
        if name in registry:
            del registry[name]
            self._save_registry(registry)
