from __future__ import annotations

import json
import os
from dataclasses import dataclass
from urllib.parse import urlparse

from src.errors import UserError

GBP_LOCATION_ID_MAP_ENV_VAR = "GBP_LOCATION_ID_MAP"
GBP_PUBLISH_DRY_RUN_ENV_VAR = "GBP_PUBLISH_DRY_RUN"
GBP_BASE_URL_ENV_VAR = "GBP_BASE_URL"
GBP_ALLOW_INSECURE_HTTP_ENV_VAR = "GBP_ALLOW_INSECURE_HTTP"
GBP_ACCOUNT_ID_ENV_VAR = "GBP_ACCOUNT_ID"
GBP_ACCESS_TOKEN_ENV_VAR = "GBP_ACCESS_TOKEN"

DEFAULT_GBP_BASE_URL = "https://mybusiness.googleapis.com/v4"


@dataclass(frozen=True)
class GbpConfig:
    location_id_map: dict[str, str]
    publish_dry_run_default: bool
    base_url: str
    account_id: str | None
    access_token: str | None


def load_gbp_config(*, require_auth: bool) -> GbpConfig:
    location_id_map = _load_location_id_map()
    publish_dry_run_default = _load_bool(GBP_PUBLISH_DRY_RUN_ENV_VAR, default=True)
    base_url = _load_base_url()
    account_id = (
        _load_required_str(GBP_ACCOUNT_ID_ENV_VAR)
        if require_auth
        else _load_optional_str(GBP_ACCOUNT_ID_ENV_VAR)
    )
    access_token = (
        _load_required_str(GBP_ACCESS_TOKEN_ENV_VAR)
        if require_auth
        else _load_optional_str(GBP_ACCESS_TOKEN_ENV_VAR)
    )
    return GbpConfig(
        location_id_map=location_id_map,
        publish_dry_run_default=publish_dry_run_default,
        base_url=base_url,
        account_id=account_id,
        access_token=access_token,
    )


def normalize_location_key(value: str) -> str:
    return " ".join(str(value or "").strip().lower().split())


def _load_location_id_map() -> dict[str, str]:
    raw = os.environ.get(GBP_LOCATION_ID_MAP_ENV_VAR, "").strip()
    if not raw:
        raise UserError(
            f"Missing {GBP_LOCATION_ID_MAP_ENV_VAR}. Provide JSON mapping of place -> GBP location id."
        )
    try:
        data = json.loads(raw)
    except json.JSONDecodeError as exc:
        raise UserError(f"Invalid JSON in {GBP_LOCATION_ID_MAP_ENV_VAR}: {exc.msg}") from exc
    if not isinstance(data, dict):
        raise UserError(f"{GBP_LOCATION_ID_MAP_ENV_VAR} must be a JSON object.")

    out: dict[str, str] = {}
    for k, v in data.items():
        key = normalize_location_key(str(k))
        val = str(v).strip()
        if not key or not val:
            continue
        out[key] = val
    if not out:
        raise UserError(f"{GBP_LOCATION_ID_MAP_ENV_VAR} is empty after normalization.")
    return out


def _load_base_url() -> str:
    raw = os.environ.get(GBP_BASE_URL_ENV_VAR, "").strip() or DEFAULT_GBP_BASE_URL
    parsed = urlparse(raw)
    if parsed.scheme not in {"https", "http"} or not parsed.netloc:
        raise UserError(f"Invalid {GBP_BASE_URL_ENV_VAR}: {raw!r}")
    if parsed.scheme == "http" and os.environ.get(GBP_ALLOW_INSECURE_HTTP_ENV_VAR) != "1":
        raise UserError(
            "Refusing insecure http base URL for GBP. "
            f"Set {GBP_ALLOW_INSECURE_HTTP_ENV_VAR}=1 to override."
        )
    return raw.rstrip("/")


def _load_required_str(name: str) -> str:
    value = os.environ.get(name, "").strip()
    if not value:
        raise UserError(f"Missing {name}.")
    return value


def _load_optional_str(name: str) -> str | None:
    value = os.environ.get(name, "").strip()
    return value or None


def _load_bool(name: str, *, default: bool) -> bool:
    raw = os.environ.get(name, "").strip().lower()
    if not raw:
        return bool(default)
    if raw in {"1", "true", "yes", "y", "on"}:
        return True
    if raw in {"0", "false", "no", "n", "off"}:
        return False
    raise UserError(f"Invalid {name}: expected boolean string, got {raw!r}")
