from __future__ import print_function
import copy
import os
import yaml

DEFAULTS = {
    "app": {
        "enabled": True,
        "poll_seconds": 30,
        "startup_quiet": True,
        "state_db": "/var/lib/bayouradar-skywarn/state.db",
        "runtime_dir": "/run/bayouradar-skywarn",
        "log_level": "INFO",
    },
    "data_sources": {
        "primary": "bayou_radar",
        "fallback": "nws",
        "fallback_enabled": True,
    },
    "bayou_radar": {
        "api_base": "https://backend.bayouradar.com",
        "point_endpoint": "/api/alerts",
        "bbox_endpoint": "/api/alerts/active",
        "health_endpoint": "/api/health",
        "user_agent": "BayouRadarSkywarn/0.2.0",
        "timeout_seconds": 12,
        "retries": 2,
        "include_zones": True,
        "headers": {},
        "api_token": "",
        "default_bbox": None,
    },
    "areas": [],
    "nws": {
        "api_base": "https://api.weather.gov",
        "user_agent": "(BayouRadarSkywarn/0.2.0, https://bayouradar.com)",
        "timeout_seconds": 15,
        "retries": 2,
        "time_basis": "effective",
        # Retained for backward-compatible configurations. New configs should
        # use top-level areas so Bayou Radar and NWS share one area definition.
        "areas": [],
        "include_events": [],
        "exclude_events": ["Test"],
    },
    "asterisk": {
        "enabled": True,
        "binary": "/usr/sbin/asterisk",
        "nodes": [],
        "playback_command": ["{binary}", "-rx", "rpt localplay {node} {audio_base}"],
    },
    "audio": {
        "enabled": True,
        "engine": "espeak",
        "voice": "en-us",
        "rate": 155,
        "piper_model": "",
        "custom_command": [],
        "ffmpeg_binary": "/usr/bin/ffmpeg",
        "delay_ms": 0,
        "keep_generated": False,
    },
    "announcements": {
        "say_new": True,
        "say_updates": True,
        "say_clear": True,
        "say_all_clear": True,
        "include_areas": True,
        "include_expiration": True,
        "max_description_chars": 850,
        "include_events": [],
        "exclude_events": [],
        "say_all_active_on_change": False,
        "new_prefix_audio": [],
        "update_prefix_audio": [],
        "clear_prefix_audio": [],
        "suffix_audio": [],
    },
    "tail_message": {
        "enabled": True,
        "path": "/run/bayouradar-skywarn/wx-tail",
        "include_events": [],
        "exclude_events": [],
        "prefix_audio": [],
        "suffix_audio": [],
    },
    "courtesy_tones": {"enabled": False, "trigger_events": [], "files": {}},
    "id_change": {"enabled": False, "trigger_events": [], "normal": "", "weather": "", "target": ""},
    "actions": {"enabled": False, "mappings": [], "active_commands": [], "inactive_commands": []},
    "notifications": {"pushover": {"enabled": False, "user_key": "", "api_token": ""}},
    "supermon": {"enabled": True, "warnings_path": "/tmp/AUTOSKY/warnings.txt"},
    "web": {"enabled": True, "bind": "127.0.0.1", "port": 8765},
    "testing": {"inject": False, "alerts": []},
}


def deep_merge(base, override):
    result = copy.deepcopy(base)
    for key, value in (override or {}).items():
        if isinstance(value, dict) and isinstance(result.get(key), dict):
            result[key] = deep_merge(result[key], value)
        else:
            result[key] = value
    return result


def area_definitions(cfg):
    configured = cfg.get("areas") or []
    if configured:
        return configured
    return cfg.get("nws", {}).get("areas") or []


def load_config(path):
    if not os.path.exists(path):
        raise RuntimeError("Configuration file not found: %s" % path)
    with open(path, "r") as handle:
        loaded = yaml.safe_load(handle) or {}
    cfg = deep_merge(DEFAULTS, loaded)
    validate_config(cfg)
    cfg["_path"] = os.path.abspath(path)
    return cfg


def validate_config(cfg):
    errors = []
    if int(cfg["app"].get("poll_seconds", 0)) < 30:
        errors.append("app.poll_seconds must be at least 30 seconds to respect NWS guidance")
    sources = cfg.get("data_sources") or {}
    if str(sources.get("primary", "bayou_radar")).lower() != "bayou_radar":
        errors.append("data_sources.primary must be bayou_radar")
    if str(sources.get("fallback", "nws")).lower() != "nws":
        errors.append("data_sources.fallback must be nws")
    basis = str(cfg["nws"].get("time_basis", "effective")).lower()
    if basis not in ("effective", "onset"):
        errors.append("nws.time_basis must be effective or onset")
    areas = area_definitions(cfg)
    if not areas and not cfg["testing"].get("inject"):
        errors.append("areas must contain at least one monitored county/zone")
    for item in areas:
        code = item.get("code") if isinstance(item, dict) else item
        if not code or len(str(code).strip()) < 5:
            errors.append("Invalid NWS fallback area code: %r" % (code,))
        if isinstance(item, dict):
            nested = item.get("bayou_radar") if isinstance(item.get("bayou_radar"), dict) else {}
            bbox = nested.get("bbox") or item.get("bbox")
            lat = nested.get("latitude", nested.get("lat", item.get("latitude", item.get("lat"))))
            lon = nested.get("longitude", nested.get("lon", item.get("longitude", item.get("lon"))))
            if bbox is not None:
                if not isinstance(bbox, dict) or any(bbox.get(key) is None for key in ("west", "south", "east", "north")):
                    errors.append("Area %s has an incomplete Bayou Radar bbox" % code)
            elif (lat is None) != (lon is None):
                errors.append("Area %s must provide both latitude and longitude" % code)
    if cfg["asterisk"].get("enabled") and not cfg["asterisk"].get("nodes"):
        errors.append("asterisk.nodes must contain at least one node when Asterisk output is enabled")
    if errors:
        raise RuntimeError("Configuration error:\n- " + "\n- ".join(errors))


def configuration_warnings(cfg):
    warnings = []
    default_bbox = (cfg.get("bayou_radar") or {}).get("default_bbox")
    for item in area_definitions(cfg):
        if not isinstance(item, dict):
            warnings.append("Area %s has no Bayou Radar locator and will use NWS fallback" % item)
            continue
        code = str(item.get("code") or "unknown").upper()
        nested = item.get("bayou_radar") if isinstance(item.get("bayou_radar"), dict) else {}
        bbox = nested.get("bbox") or item.get("bbox") or default_bbox
        lat = nested.get("latitude", nested.get("lat", item.get("latitude", item.get("lat"))))
        lon = nested.get("longitude", nested.get("lon", item.get("longitude", item.get("lon"))))
        if not isinstance(bbox, dict) and (lat is None or lon is None):
            warnings.append("Area %s has no Bayou Radar locator and will use NWS fallback" % code)
        elif not isinstance(bbox, dict):
            warnings.append("Area %s uses Bayou Radar point mode; use a bbox for whole-county coverage" % code)
    return warnings


def area_map(cfg):
    result = {}
    for item in area_definitions(cfg):
        if isinstance(item, dict):
            code = str(item.get("code", "")).strip().upper()
            result[code] = str(item.get("name") or code)
        else:
            code = str(item).strip().upper()
            result[code] = code
    return result
