from __future__ import annotations

from pathlib import Path
import json
import re

import requests

from fastgws.errors import DiscoveryError, ValidationError

_safe_id = re.compile(r"^[A-Za-z0-9_.-]+$")


def validate_identifier(value: str, label: str) -> str:
    if not _safe_id.fullmatch(value):
        raise ValidationError(f"invalid {label}: {value!r}")
    return value


def discovery_urls(service: str, version: str) -> list[tuple[str, dict | None]]:
    service = validate_identifier(service, "service")
    version = validate_identifier(version, "version")
    return [
        (f"https://www.googleapis.com/discovery/v1/apis/{service}/{version}/rest", None),
        (f"https://{service}.googleapis.com/$discovery/rest", {"version": version}),
    ]


def cache_path(cache_dir: str | Path, service: str, version: str) -> Path:
    return Path(cache_dir) / f"{service}_{version}.json"


def read_cache(cache_dir: str | Path, service: str, version: str):
    path = cache_path(cache_dir, service, version)
    if not path.exists():
        return None
    return json.loads(path.read_text())


def write_cache(cache_dir: str | Path, service: str, version: str, doc: dict):
    path = cache_path(cache_dir, service, version)
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(doc, indent=2, sort_keys=True) + "\n")
    return path


def fetch_live_doc(service: str, version: str, *, client=None, timeout=30) -> dict:
    session = client or requests.Session()
    last_status = None
    for url, params in discovery_urls(service, version):
        response = session.get(url, params=params, timeout=timeout)
        last_status = response.status_code
        if response.ok:
            return response.json()
    raise DiscoveryError(f"failed to fetch discovery doc for {service}/{version}: HTTP {last_status}")


def fetch_discovery_doc(service: str, version: str, *, client=None, cache_dir: str | Path | None = None) -> dict:
    validate_identifier(service, "service")
    validate_identifier(version, "version")
    if cache_dir:
        cached = read_cache(cache_dir, service, version)
        if cached is not None:
            return cached
    doc = fetch_live_doc(service, version, client=client)
    if cache_dir:
        write_cache(cache_dir, service, version, doc)
    return doc
