"""NEUROVAULT configuration loading."""

from __future__ import annotations

import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

try:  # Python 3.11+
    import tomllib
except ModuleNotFoundError:  # pragma: no cover
    import tomli as tomllib  # type: ignore[no-redef]


@dataclass
class NeurovaultConfig:
    """Runtime configuration for sensors, privacy, and consolidation."""

    sensors: dict[str, Any] = field(
        default_factory=lambda: {
            "clipboard": {"enabled": True, "poll_interval": 2.0},
            "file_watcher": {
                "enabled": True,
                "watch_paths": ["~/Documents", "~/Desktop", "~/Projects"],
                "poll_interval": 2.0,
            },
            "browser": {"enabled": True, "poll_interval": 300},
            "screenshot": {"enabled": False, "poll_interval": 300},
            "continuous_screen": {"enabled": False, "poll_interval": 5.0},
            "audio": {"enabled": False},
            "calendar": {"enabled": True, "poll_interval": 900},
            "git": {"enabled": True, "poll_interval": 600},
        }
    )
    privacy: dict[str, Any] = field(
        default_factory=lambda: {
            "url_blocklist": [
                "*://*.bank.com/*",
                "*://mail.google.com/*",
                "*://*.paypal.com/*",
                "*://github.com/settings/*",
            ],
            "app_blocklist": ["1Password", "Keychain Access", "Signal", "Terminal"],
            "path_blocklist": ["~/.ssh/", "~/.aws/", "~/.gnupg/", "**/.env*"],
            "content_blocklist": [],
        }
    )
    consolidation: dict[str, Any] = field(
        default_factory=lambda: {
            "idle_threshold_seconds": 300,
            "cluster_threshold": 0.6,
            "min_cluster_size": 3,
            "llm_enabled": False,
            "version": "v2",
            "bootstrap_ontology": True,
        }
    )
    plugin_configs: dict[str, Any] = field(default_factory=dict)
    encryption: dict[str, Any] = field(
        default_factory=lambda: {"enabled": False, "key_source": "keychain"}
    )
    llm: dict[str, Any] = field(
        default_factory=lambda: {
            "enabled": False,
            "base_url": "http://127.0.0.1:11434",
            "model": "qwen2.5:3b",
        }
    )
    embedding: dict[str, Any] = field(
        default_factory=lambda: {
            "backend": "auto",
            "model": "all-MiniLM-L6-v2",
        }
    )
    dashboard: dict[str, Any] = field(default_factory=lambda: {"enabled": False, "port": 8501})
    pipeline: dict[str, Any] = field(
        default_factory=lambda: {"enabled": False, "batch_size": 16, "batch_timeout": 2.0}
    )
    observability: dict[str, Any] = field(default_factory=lambda: {"enabled": False})
    network: dict[str, Any] = field(
        default_factory=lambda: {
            "enabled": False,
            "listen_port": 9473,
            "mdns_discovery": True,
            "trusted_peers": [],
            "sync": {
                "strategy": "newer-wins",
                "auto_sync_interval": 300,
                "sync_on_consolidation": True,
            },
        }
    )
    search: dict[str, Any] = field(
        default_factory=lambda: {
            "hnsw": {
                "enabled": True,
                "ef_construction": 200,
                "M": 16,
                "ef_search": 50,
                "rebuild_threshold": 10000,
                "max_elements": 1_000_000,
            }
        }
    )
    api: dict[str, Any] = field(
        default_factory=lambda: {
            "mobile": {"enabled": False, "port": 9474, "auth_method": "jwt", "read_only": False},
            "websocket": {"enabled": False, "port": 9475, "max_connections": 10},
            "graphql": {"enabled": False, "port": 9476},
        }
    )
    monitoring: dict[str, Any] = field(default_factory=lambda: {"prometheus": False, "port": 9477})
    disabled_sensors: list[str] = field(default_factory=list)

    @classmethod
    def load(cls, path: str | None = None) -> "NeurovaultConfig":
        """Load config from ~/.neurovault/config.toml, then apply env overrides."""
        cfg = cls()

        config_path = Path(path).expanduser() if path else Path.home() / ".neurovault" / "config.toml"
        if config_path.exists():
            raw = tomllib.loads(config_path.read_text())
            cfg._merge(raw)

        if os.environ.get("NEUROVAULT_NO_SENSORS"):
            cfg.disabled_sensors = [
                "clipboard",
                "file_watcher",
                "browser",
                "screenshot",
                "audio",
                "calendar",
                "git",
                "continuous_screen",
            ]

        return cfg

    def get(self, key: str, default: Any = None) -> Any:
        """Dict-style convenience accessor."""
        return getattr(self, key, default)

    def set_sensor_enabled(self, sensor_name: str, enabled: bool) -> None:
        sensor_cfg = self.sensors.setdefault(sensor_name, {})
        if isinstance(sensor_cfg, dict):
            sensor_cfg["enabled"] = enabled
        if enabled:
            self.disabled_sensors = [s for s in self.disabled_sensors if s != sensor_name]
        elif sensor_name not in self.disabled_sensors:
            self.disabled_sensors.append(sensor_name)

    def save(self, path: str | None = None) -> None:
        target = Path(path).expanduser() if path else Path.home() / ".neurovault" / "config.toml"
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_text(_to_toml(self.to_dict()))

    def to_dict(self) -> dict[str, Any]:
        return {
            "sensors": self.sensors,
            "privacy": self.privacy,
            "consolidation": self.consolidation,
            "plugin_configs": self.plugin_configs,
            "encryption": self.encryption,
            "llm": self.llm,
            "embedding": self.embedding,
            "dashboard": self.dashboard,
            "pipeline": self.pipeline,
            "observability": self.observability,
            "network": self.network,
            "search": self.search,
            "api": self.api,
            "monitoring": self.monitoring,
            "disabled_sensors": self.disabled_sensors,
        }

    def _merge(self, override: dict[str, Any]) -> None:
        if not isinstance(override, dict):
            return
        for top_key in (
            "sensors",
            "privacy",
            "consolidation",
            "plugin_configs",
            "encryption",
            "llm",
            "embedding",
            "dashboard",
            "pipeline",
            "observability",
            "network",
            "search",
            "api",
            "monitoring",
        ):
            section = override.get(top_key)
            if isinstance(section, dict):
                current = getattr(self, top_key)
                current.update(section)

        disabled = override.get("disabled_sensors")
        if isinstance(disabled, list):
            self.disabled_sensors = [str(s) for s in disabled]


def _toml_scalar(value: Any) -> str:
    if isinstance(value, bool):
        return "true" if value else "false"
    if isinstance(value, (int, float)):
        return str(value)
    if value is None:
        return '""'
    s = str(value).replace("\\", "\\\\").replace('"', '\\"')
    return f'"{s}"'


def _to_toml(data: dict[str, Any]) -> str:
    lines: list[str] = []
    top_scalars: dict[str, Any] = {}
    top_tables: dict[str, dict[str, Any]] = {}

    for key, value in data.items():
        if isinstance(value, dict):
            top_tables[key] = value
        else:
            top_scalars[key] = value

    for key, value in top_scalars.items():
        if isinstance(value, list):
            list_values = ", ".join(_toml_scalar(v) for v in value)
            lines.append(f"{key} = [{list_values}]")
        else:
            lines.append(f"{key} = {_toml_scalar(value)}")

    for table_name, table in top_tables.items():
        lines.append("")
        lines.append(f"[{table_name}]")
        for key, value in table.items():
            if isinstance(value, dict):
                lines.append("")
                lines.append(f"[{table_name}.{key}]")
                for sk, sv in value.items():
                    if isinstance(sv, list):
                        list_values = ", ".join(_toml_scalar(v) for v in sv)
                        lines.append(f"{sk} = [{list_values}]")
                    else:
                        lines.append(f"{sk} = {_toml_scalar(sv)}")
            elif isinstance(value, list):
                list_values = ", ".join(_toml_scalar(v) for v in value)
                lines.append(f"{key} = [{list_values}]")
            else:
                lines.append(f"{key} = {_toml_scalar(value)}")

    return "\n".join(lines).strip() + "\n"
