"""Shared YAML-backed configuration helpers for account credentials."""

from __future__ import annotations

import os
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Mapping, MutableMapping, Optional

try:
    import yaml
except Exception as exc:  # pragma: no cover - dependency should be installed
    yaml = None  # type: ignore[assignment]
    _YAML_IMPORT_ERROR = exc
else:
    _YAML_IMPORT_ERROR = None

CONFIG_ENV_VAR = "SOIGIA_CONFIG_PATH"
DEFAULT_CONFIG_FILENAME = "config.yaml"
DEFAULT_ACCOUNT_TYPES = ("google_sheets", "binance", "mt5", "telegram", "shopee")


def _utc_now_iso() -> str:
    return datetime.now(timezone.utc).replace(microsecond=0).isoformat()


def _require_yaml() -> None:
    if yaml is None:  # pragma: no cover - dependency should be installed
        raise RuntimeError("PyYAML is required to load/save config files") from _YAML_IMPORT_ERROR


def resolve_config_path(path: Optional[os.PathLike[str] | str] = None) -> Path:
    if path is not None:
        return Path(path).expanduser().resolve()
    env_path = os.environ.get(CONFIG_ENV_VAR)
    if env_path:
        return Path(env_path).expanduser().resolve()
    return (Path.cwd() / DEFAULT_CONFIG_FILENAME).resolve()


def _default_account_bucket() -> dict[str, dict[str, Any]]:
    return {account_type: {} for account_type in DEFAULT_ACCOUNT_TYPES}


def _default_meta() -> dict[str, Any]:
    return {
        "created_at": _utc_now_iso(),
        "updated_at": _utc_now_iso(),
        "description": "Shared account configuration for API, secrets, and integrations.",
    }


def default_config() -> dict[str, Any]:
    return {
        "version": 1,
        "meta": _default_meta(),
        "accounts": _default_account_bucket(),
    }


def _normalize_mapping(value: Any) -> dict[str, Any]:
    return dict(value) if isinstance(value, Mapping) else {}


def _normalize_account_entry(entry: Any, *, service: str, name: str) -> dict[str, Any]:
    if not isinstance(entry, Mapping):
        entry = {}
    normalized = {
        "service": service,
        "name": name,
        "enabled": bool(entry.get("enabled", True)),
        "label": str(entry.get("label", name)),
        "credentials": _normalize_mapping(entry.get("credentials")),
        "settings": _normalize_mapping(entry.get("settings")),
        "notes": str(entry.get("notes", "")),
        "updated_at": str(entry.get("updated_at", _utc_now_iso())),
    }
    for key, value in entry.items():
        if key not in normalized and key not in {"credentials", "settings"}:
            normalized[key] = value
    return normalized


def normalize_config(data: Any) -> dict[str, Any]:
    config = default_config()
    if not isinstance(data, Mapping):
        return config

    for key in ("version", "meta"):
        if key in data:
            if key == "meta":
                merged_meta = _default_meta()
                merged_meta.update(_normalize_mapping(data[key]))
                config[key] = merged_meta
            else:
                config[key] = data[key]

    accounts = data.get("accounts", {})
    if isinstance(accounts, Mapping):
        merged_accounts: dict[str, dict[str, Any]] = _default_account_bucket()
        for service, service_accounts in accounts.items():
            service_name = str(service)
            bucket: dict[str, Any] = {}
            if isinstance(service_accounts, Mapping):
                for account_name, entry in service_accounts.items():
                    account_name_str = str(account_name)
                    bucket[account_name_str] = _normalize_account_entry(
                        entry,
                        service=service_name,
                        name=account_name_str,
                    )
            merged_accounts[service_name] = bucket
        config["accounts"] = merged_accounts
    return config


def load_config(path: Optional[os.PathLike[str] | str] = None, *, create: bool = False) -> dict[str, Any]:
    _require_yaml()
    config_path = resolve_config_path(path)
    if not config_path.exists():
        config = default_config()
        if create:
            save_config(config, config_path)
        return config
    with config_path.open("r", encoding="utf-8") as handle:
        loaded = yaml.safe_load(handle) or {}
    return normalize_config(loaded)


def save_config(data: Mapping[str, Any], path: Optional[os.PathLike[str] | str] = None) -> Path:
    _require_yaml()
    config_path = resolve_config_path(path)
    config_path.parent.mkdir(parents=True, exist_ok=True)
    normalized = normalize_config(data)
    normalized["meta"]["updated_at"] = _utc_now_iso()
    temp_path = config_path.with_suffix(config_path.suffix + ".tmp")
    with temp_path.open("w", encoding="utf-8") as handle:
        yaml.safe_dump(normalized, handle, sort_keys=False, allow_unicode=True)
    temp_path.replace(config_path)
    return config_path


def get_accounts(config: Mapping[str, Any], service: str) -> dict[str, Any]:
    accounts = config.get("accounts", {})
    if not isinstance(accounts, Mapping):
        return {}
    bucket = accounts.get(service, {})
    return dict(bucket) if isinstance(bucket, Mapping) else {}


def get_account(config: Mapping[str, Any], service: str, name: str) -> dict[str, Any] | None:
    return get_accounts(config, service).get(name)


def upsert_account(
    config: MutableMapping[str, Any],
    service: str,
    name: str,
    account: Mapping[str, Any],
) -> MutableMapping[str, Any]:
    normalized = normalize_config(config)
    accounts = normalized.setdefault("accounts", _default_account_bucket())
    service_bucket = accounts.setdefault(service, {})
    service_bucket[name] = _normalize_account_entry(account, service=service, name=name)
    normalized["meta"]["updated_at"] = _utc_now_iso()
    config.clear()
    config.update(normalized)
    return config


def delete_account(config: MutableMapping[str, Any], service: str, name: str) -> MutableMapping[str, Any]:
    normalized = normalize_config(config)
    accounts = normalized.setdefault("accounts", _default_account_bucket())
    service_bucket = accounts.setdefault(service, {})
    if name in service_bucket:
        del service_bucket[name]
        normalized["meta"]["updated_at"] = _utc_now_iso()
    config.clear()
    config.update(normalized)
    return config


@dataclass(slots=True)
class ConfigStore:
    path: Path | str | None = None

    def resolved_path(self) -> Path:
        return resolve_config_path(self.path)

    def load(self, *, create: bool = False) -> dict[str, Any]:
        return load_config(self.resolved_path(), create=create)

    def save(self, data: Mapping[str, Any]) -> Path:
        return save_config(data, self.resolved_path())

    def get_accounts(self, service: str) -> dict[str, Any]:
        return get_accounts(self.load(), service)

    def get_account(self, service: str, name: str) -> dict[str, Any] | None:
        return get_account(self.load(), service, name)

    def upsert_account(self, service: str, name: str, account: Mapping[str, Any]) -> Path:
        data = self.load()
        upsert_account(data, service, name, account)
        return self.save(data)

    def delete_account(self, service: str, name: str) -> Path:
        data = self.load()
        delete_account(data, service, name)
        return self.save(data)
