#!/usr/bin/env python3
"""
Config Template System — Environment-Specific Overlays (Issue #696)

Loads base.yaml + {env}.overlay.yaml with deep merge.
Overlay keys override base keys. Supports dot notation access.

Usage:
    from scripts.config_template import ConfigTemplate, load_config

    config = load_config("dev")
    template = ConfigTemplate()
    template.load("prod")
    model = template.get("model.name")

CLI:
    python3 scripts/config_template.py --env prod
    python3 scripts/config_template.py --env dev --diff
    python3 scripts/config_template.py --env prod --validate
    python3 scripts/config_template.py --list-envs
"""
import argparse
import copy
import json
import os
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional

try:
    import yaml
except ImportError:
    yaml = None


CONFIG_DIR = Path(__file__).resolve().parent.parent / "config"
KNOWN_ENVS = ("dev", "prod", "cron", "gateway")


def _deep_merge(base: dict, overlay: dict) -> dict:
    """Deep merge overlay into base. Overlay values win on conflict."""
    result = copy.deepcopy(base)
    for key, value in overlay.items():
        if key in result and isinstance(result[key], dict) and isinstance(value, dict):
            result[key] = _deep_merge(result[key], value)
        else:
            result[key] = copy.deepcopy(value)
    return result


def _get_dotted(data: dict, key: str, default: Any = None) -> Any:
    """Get value from dict using dot notation: 'model.name' -> data['model']['name']."""
    parts = key.split(".")
    current = data
    for part in parts:
        if isinstance(current, dict) and part in current:
            current = current[part]
        else:
            return default
    return current


def _diff_dicts(base: dict, overlay: dict, prefix: str = "") -> List[dict]:
    """Compute diff between base and overlay configs."""
    diffs = []
    all_keys = set(list(base.keys()) + list(overlay.keys()))
    for key in sorted(all_keys):
        path = f"{prefix}.{key}" if prefix else key
        in_base = key in base
        in_overlay = key in overlay
        if in_base and not in_overlay:
            diffs.append({"key": path, "type": "removed_in_overlay", "base": base[key]})
        elif not in_base and in_overlay:
            diffs.append({"key": path, "type": "added_in_overlay", "overlay": overlay[key]})
        elif isinstance(base[key], dict) and isinstance(overlay[key], dict):
            diffs.extend(_diff_dicts(base[key], overlay[key], path))
        elif base[key] != overlay[key]:
            diffs.append({
                "key": path, "type": "changed",
                "base": base[key], "overlay": overlay[key]
            })
    return diffs


def _validate_config(config: dict) -> List[str]:
    """Validate config structure, return list of warnings."""
    warnings = []
    if "model" not in config:
        warnings.append("Missing 'model' section")
    elif "name" not in config.get("model", {}):
        warnings.append("Missing 'model.name'")
    if "provider" not in config:
        warnings.append("Missing 'provider' section")
    for key in config:
        if not isinstance(key, str):
            warnings.append(f"Non-string key: {key!r}")
    return warnings


def _load_yaml_file(path: Path) -> dict:
    """Load a YAML file, return empty dict if missing."""
    if not path.exists():
        return {}
    if yaml is None:
        raise ImportError("PyYAML required: pip install pyyaml")
    with open(path) as f:
        data = yaml.safe_load(f)
    return data if isinstance(data, dict) else {}


class ConfigTemplate:
    """Environment-specific config template with overlay merge."""

    def __init__(self, config_dir: Optional[str] = None):
        self.config_dir = Path(config_dir) if config_dir else CONFIG_DIR
        self.base: Dict[str, Any] = {}
        self.overlay: Dict[str, Any] = {}
        self.merged: Dict[str, Any] = {}
        self.env: Optional[str] = None

    def load(self, env: str) -> dict:
        """Load base + overlay for the given environment."""
        if env not in KNOWN_ENVS:
            raise ValueError(f"Unknown environment '{env}'. Known: {', '.join(KNOWN_ENVS)}")
        self.env = env
        self.base = _load_yaml_file(self.config_dir / "base.yaml")
        self.overlay = _load_yaml_file(self.config_dir / f"{env}.overlay.yaml")
        self.merged = _deep_merge(self.base, self.overlay)
        return self.merged

    def get(self, key: str, default: Any = None) -> Any:
        """Get value with dot notation from merged config."""
        return _get_dotted(self.merged, key, default)

    def diff(self) -> List[dict]:
        """Show diff between base and current overlay."""
        return _diff_dicts(self.base, self.overlay)

    def validate(self) -> List[str]:
        """Validate merged config structure."""
        return _validate_config(self.merged)

    @staticmethod
    def list_environments() -> List[str]:
        """List known environments."""
        return list(KNOWN_ENVS)


def load_config(env: str, config_dir: Optional[str] = None) -> dict:
    """One-shot: load merged config for an environment."""
    t = ConfigTemplate(config_dir)
    return t.load(env)


def main():
    parser = argparse.ArgumentParser(description="Config Template System")
    parser.add_argument("--env", default="dev", help="Environment name")
    parser.add_argument("--diff", action="store_true", help="Show diff between base and overlay")
    parser.add_argument("--validate", action="store_true", help="Validate merged config")
    parser.add_argument("--list-envs", action="store_true", help="List known environments")
    parser.add_argument("--config-dir", default=None, help="Config directory path")
    parser.add_argument("--json", action="store_true", help="Output as JSON")
    args = parser.parse_args()

    if args.list_envs:
        envs = ConfigTemplate.list_environments()
        if args.json:
            print(json.dumps(envs, indent=2))
        else:
            for e in envs:
                print(f"  {e}")
        return

    template = ConfigTemplate(args.config_dir)
    template.load(args.env)

    if args.diff:
        diffs = template.diff()
        if args.json:
            print(json.dumps(diffs, indent=2))
        else:
            if not diffs:
                print(f"No differences between base and {args.env} overlay")
            for d in diffs:
                if d["type"] == "changed":
                    print(f"  {d['key']}: {d['base']!r} -> {d['overlay']!r}")
                elif d["type"] == "added_in_overlay":
                    print(f"  {d['key']}: + {d['overlay']!r}")
                elif d["type"] == "removed_in_overlay":
                    print(f"  {d['key']}: - {d['base']!r}")
    elif args.validate:
        warnings = template.validate()
        if args.json:
            print(json.dumps({"valid": len(warnings) == 0, "warnings": warnings}, indent=2))
        else:
            if warnings:
                for w in warnings:
                    print(f"  WARNING: {w}")
            else:
                print(f"  Config valid for {args.env}")
    else:
        if args.json:
            print(json.dumps(template.merged, indent=2))
        else:
            print(f"Config for {args.env}:")
            for k, v in template.merged.items():
                print(f"  {k}: {v!r}")


if __name__ == "__main__":
    main()
