"""YAML config loader with defaults merging and validation."""

from __future__ import annotations

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

import yaml

DEFAULT_PING_SERVICES = [
    "http://rpc.pingomatic.com/",
    "https://rpc.twingly.com/",
    "http://ping.blo.gs/",
]

DEFAULT_PUBSUBHUBBUB_HUBS = [
    "https://pubsubhubbub.appspot.com/",
    "https://pubsubhubbub.superfeedr.com/",
]
DEFAULT_INDEXNOW_ENDPOINT = "https://api.indexnow.org/indexnow"


@dataclass
class SiteConfig:
    slug: str
    name: str
    feed_url: str
    site_url: str
    indexnow_key: str | None = None
    ping_services: list[str] = field(default_factory=list)
    pubsubhubbub_hubs: list[str] = field(default_factory=list)
    indexnow_endpoint: str = DEFAULT_INDEXNOW_ENDPOINT


@dataclass
class GoogleConfig:
    service_account_json: str | None = None

    @property
    def resolved_path(self) -> Path | None:
        if not self.service_account_json:
            return None
        return Path(os.path.expanduser(self.service_account_json))


@dataclass
class Config:
    sites: dict[str, SiteConfig]
    google: GoogleConfig

    def get_site(self, slug: str) -> SiteConfig:
        if slug not in self.sites:
            available = ", ".join(sorted(self.sites.keys()))
            raise ConfigError(f"Site '{slug}' not found. Available: {available}")
        return self.sites[slug]

    def all_sites(self) -> list[SiteConfig]:
        return list(self.sites.values())


class ConfigError(Exception):
    pass


def load_config(path: str | Path) -> Config:
    """Load and validate the YAML config file."""
    path = Path(path)
    if not path.exists():
        raise ConfigError(f"Config file not found: {path}")

    with open(path) as f:
        raw = yaml.safe_load(f)

    if not isinstance(raw, dict):
        raise ConfigError("Config must be a YAML mapping")

    return _parse_config(raw)


def find_config() -> Path:
    """Find config file: ./sites.yaml, then ~/.config/ping-rss/sites.yaml."""
    candidates = [
        Path.cwd() / "sites.yaml",
        Path.home() / ".config" / "ping-rss" / "sites.yaml",
    ]
    for p in candidates:
        if p.exists():
            return p
    raise ConfigError(
        "No sites.yaml found. Searched:\n"
        + "\n".join(f"  - {p}" for p in candidates)
    )


def _parse_config(raw: dict[str, Any]) -> Config:
    defaults = raw.get("defaults", {})
    default_ping = defaults.get("ping_services", DEFAULT_PING_SERVICES)
    default_hubs = defaults.get("pubsubhubbub_hubs", DEFAULT_PUBSUBHUBBUB_HUBS)
    default_indexnow_ep = defaults.get("indexnow_endpoint", DEFAULT_INDEXNOW_ENDPOINT)

    # Google config
    google_raw = raw.get("google", {})
    google = GoogleConfig(
        service_account_json=google_raw.get("service_account_json"),
    )

    # Sites
    sites_raw = raw.get("sites", {})
    if not sites_raw:
        raise ConfigError("Config must define at least one site under 'sites:'")

    sites: dict[str, SiteConfig] = {}
    for slug, site_data in sites_raw.items():
        if not isinstance(site_data, dict):
            raise ConfigError(f"Site '{slug}' must be a mapping")

        for required in ("name", "feed_url", "site_url"):
            if required not in site_data:
                raise ConfigError(f"Site '{slug}' missing required field: {required}")

        sites[slug] = SiteConfig(
            slug=slug,
            name=site_data["name"],
            feed_url=site_data["feed_url"],
            site_url=site_data["site_url"],
            indexnow_key=site_data.get("indexnow_key"),
            ping_services=site_data.get("ping_services", default_ping),
            pubsubhubbub_hubs=site_data.get("pubsubhubbub_hubs", default_hubs),
            indexnow_endpoint=site_data.get("indexnow_endpoint", default_indexnow_ep),
        )

    return Config(sites=sites, google=google)
