from __future__ import annotations

import json
import os
import ssl
from dataclasses import dataclass
from typing import Any
from urllib.error import HTTPError, URLError
from urllib.parse import urlparse
from urllib.request import Request, urlopen

_SSL_CTX = ssl.create_default_context()
try:
    import certifi

    _SSL_CTX.load_verify_locations(certifi.where())
except ImportError:
    pass

_ALLOWED_HOSTS = {"127.0.0.1", "localhost"}
# Release (`com.mugi.app`) uses 8324; Debug (`com.mugi.app.debug`) uses 8325 — see CalendarService.calendarPort.
_ALLOWED_BRIDGE_PORTS = frozenset({8324, 8325})


@dataclass(frozen=True)
class MacOSBridgeConfig:
    base_url: str
    auth_token: str


def validate_bridge_base_url(raw_url: str) -> tuple[bool, str]:
    u = (raw_url or "").strip()
    if not u:
        return False, "macOS bridge URL is not set (MUGI_CALENDAR_URL)."

    try:
        parsed = urlparse(u)
    except Exception:
        return False, "Invalid macOS bridge URL in MUGI_CALENDAR_URL."

    if parsed.scheme.lower() != "http":
        return False, "Invalid macOS bridge URL: only http:// is allowed."
    if (parsed.hostname or "").lower() not in _ALLOWED_HOSTS:
        return False, "Invalid macOS bridge URL: host must be localhost or 127.0.0.1."
    if parsed.port not in _ALLOWED_BRIDGE_PORTS:
        return False, "Invalid macOS bridge URL: port must be 8324 or 8325."
    if parsed.username or parsed.password:
        return False, "Invalid macOS bridge URL: credentials are not allowed."
    return True, u.rstrip("/")


def get_bridge_config() -> tuple[MacOSBridgeConfig | None, str | None]:
    ok, url_or_error = validate_bridge_base_url(os.environ.get("MUGI_CALENDAR_URL", ""))
    if not ok:
        return None, url_or_error

    token = os.environ.get("MUGI_AUTH_TOKEN", "").strip()
    if not token:
        return None, "macOS bridge auth token is not set (MUGI_AUTH_TOKEN)."

    return MacOSBridgeConfig(base_url=url_or_error, auth_token=token), None


def _make_headers(cfg: MacOSBridgeConfig, headers: dict[str, str] | None = None) -> dict[str, str]:
    out = {"X-Auth-Token": cfg.auth_token}
    if headers:
        out.update(headers)
    return out


def bridge_request(
    cfg: MacOSBridgeConfig,
    path: str,
    *,
    method: str = "GET",
    headers: dict[str, str] | None = None,
    body: bytes | None = None,
    timeout: float = 15.0,
) -> tuple[int, bytes, str]:
    req = Request(
        f"{cfg.base_url}{path}",
        data=body,
        headers=_make_headers(cfg, headers),
        method=method,
    )
    try:
        with urlopen(req, timeout=timeout, context=_SSL_CTX) as resp:
            return int(resp.getcode() or 200), resp.read(), ""
    except HTTPError as e:
        payload = b""
        try:
            payload = e.read() or b""
        except Exception:
            pass
        return int(getattr(e, "code", 500) or 500), payload, ""
    except (URLError, OSError) as e:
        return 0, b"", str(e)


def bridge_request_json(
    cfg: MacOSBridgeConfig,
    path: str,
    *,
    method: str = "GET",
    payload: dict[str, Any] | None = None,
    timeout: float = 15.0,
) -> tuple[int, dict[str, Any] | None, str | None]:
    body = None if payload is None else json.dumps(payload).encode("utf-8")
    status, raw, err = bridge_request(
        cfg,
        path,
        method=method,
        headers={"Content-Type": "application/json"} if body is not None else None,
        body=body,
        timeout=timeout,
    )
    if err:
        return status, None, err
    if not raw:
        return status, {}, None
    try:
        return status, json.loads(raw.decode("utf-8")), None
    except Exception:
        return status, None, "Bridge returned invalid JSON."
