"""
FoundryGraph Serving Layer.

Downloads DuckDB artifact from GCS and serves queries.
Implements Non-Negotiable #1: GCS artifact pattern.

Configuration:
    FM_FOUNDRYGRAPH_BUCKET: GCS bucket name (default: foundrygraph-serving)
    FM_FOUNDRYGRAPH_CACHE: Local cache directory (default: /tmp/foundrygraph)
    FM_FOUNDRYGRAPH_REFRESH_HOURS: Refresh interval in hours (default: 1)
    FM_FOUNDRYGRAPH_LOCAL_CACHE: Enable local caching (default: 1)
        Set to 0 to disable local caching for serverless/ephemeral environments.
        When disabled, the serving layer is disabled and callers should fall back to BigQuery.
"""

import json
import logging
import os
import threading
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Optional, TYPE_CHECKING

import duckdb

# Lazy import google.cloud.storage to avoid startup hang when GCP credentials aren't configured
if TYPE_CHECKING:
    pass

logger = logging.getLogger(__name__)


def _is_local_cache_enabled() -> bool:
    """Check if local caching is enabled."""
    value = os.getenv("FM_FOUNDRYGRAPH_LOCAL_CACHE", "1").strip().lower()
    return value not in ("0", "false", "no", "off")


def _is_local_duckdb_disabled() -> bool:
    """Check if local DuckDB is completely disabled (use BigQuery instead)."""
    value = os.getenv("FM_DISABLE_LOCAL_FOUNDRYGRAPH", "").strip().lower()
    return value in ("1", "true", "yes", "on")


def _get_storage_client():
    """Lazily import and create GCS client to avoid startup hang."""
    try:
        from google.cloud import storage
        return storage.Client()
    except Exception as exc:
        logger.warning("GCS client unavailable: %s", exc)
        return None

GCS_SERVING_BUCKET = os.getenv("FM_FOUNDRYGRAPH_BUCKET", "foundrygraph-serving")
LOCAL_CACHE_DIR = Path(os.getenv("FM_FOUNDRYGRAPH_CACHE", "/tmp/foundrygraph"))
REFRESH_INTERVAL_HOURS = int(os.getenv("FM_FOUNDRYGRAPH_REFRESH_HOURS", "1"))

_connection: Optional[duckdb.DuckDBPyConnection] = None
_current_version: Optional[str] = None
_last_check: Optional[datetime] = None
_lock = threading.Lock()


def _get_latest_version(storage_client: Any) -> dict:
    """Fetch latest.json from GCS."""
    bucket = storage_client.bucket(GCS_SERVING_BUCKET)
    blob = bucket.blob("latest.json")
    try:
        content = blob.download_as_text()
        return json.loads(content)
    except Exception as exc:  # noqa: BLE001 - surfacing error is important
        logger.error("Failed to fetch latest.json: %s", exc)
        return {}


def _download_artifact(storage_client: Any, version_info: dict) -> Path:
    """Download DuckDB artifact from GCS to local cache."""
    LOCAL_CACHE_DIR.mkdir(parents=True, exist_ok=True)
    version = version_info["version"]
    local_path = LOCAL_CACHE_DIR / f"serving_{version}.duckdb"

    if local_path.exists():
        logger.info("Using cached artifact: %s", local_path)
        return local_path

    bucket = storage_client.bucket(GCS_SERVING_BUCKET)
    blob = bucket.blob(version_info["path"])
    logger.info("Downloading artifact v%s from GCS...", version)
    blob.download_to_filename(str(local_path))
    logger.info("Downloaded to %s (%.1f MB)", local_path, local_path.stat().st_size / 1024 / 1024)

    for old_file in LOCAL_CACHE_DIR.glob("serving_*.duckdb"):
        if old_file != local_path:
            try:
                old_file.unlink()
                logger.info("Removed old cache: %s", old_file)
            except OSError as exc:  # noqa: BLE001 - logging is enough
                logger.warning("Failed to remove old cache %s: %s", old_file, exc)

    return local_path


def _maybe_refresh() -> None:
    """Check for new version if refresh interval passed.

    If FM_FOUNDRYGRAPH_LOCAL_CACHE=0, skips GCS download and caching.
    """
    global _connection, _current_version, _last_check

    # Skip refresh if local caching is disabled
    if not _is_local_cache_enabled():
        return

    now = datetime.now(timezone.utc)
    if _last_check and (now - _last_check) < timedelta(hours=REFRESH_INTERVAL_HOURS):
        return
    _last_check = now

    try:
        storage_client = _get_storage_client()
        if not storage_client:
            return
        version_info = _get_latest_version(storage_client)
        if not version_info:
            return

        if version_info.get("version") == _current_version:
            return

        logger.info("New version available: %s (current: %s)", version_info.get("version"), _current_version)
        local_path = _download_artifact(storage_client, version_info)

        old_conn = _connection
        _connection = duckdb.connect(str(local_path), read_only=True)
        _current_version = version_info.get("version")
        if old_conn:
            old_conn.close()
        logger.info("Switched to version %s", _current_version)
    except Exception as exc:  # noqa: BLE001
        logger.error("Failed to refresh FoundryGraph: %s", exc)


def get_foundrygraph_connection() -> Optional[duckdb.DuckDBPyConnection]:
    """
    Get a DuckDB connection to the FoundryGraph serving layer.

    Thread-safe. Automatically refreshes hourly unless FM_FOUNDRYGRAPH_LOCAL_CACHE=0.

    Configuration:
        FM_DISABLE_LOCAL_FOUNDRYGRAPH=1: Completely disable local DuckDB (use BigQuery instead).
            Raises RuntimeError - callers should handle and fall back to BigQuery.
        FM_FOUNDRYGRAPH_LOCAL_CACHE=0: Disable GCS download and serving.
            Useful for serverless/ephemeral environments with unreliable local disk.
    """
    global _connection

    # Check if local DuckDB is completely disabled
    if _is_local_duckdb_disabled():
        raise RuntimeError(
            "FoundryGraph local DuckDB disabled (FM_DISABLE_LOCAL_FOUNDRYGRAPH=1). "
            "Use BigQuery FoundryGraph lookups instead."
        )

    if not _is_local_cache_enabled():
        logger.info(
            "FoundryGraph serving disabled (FM_FOUNDRYGRAPH_LOCAL_CACHE=0). "
            "Use BigQuery FoundryGraph lookups instead."
        )
        return None

    with _lock:
        if _connection is None:
            _maybe_refresh()
            if _connection is None:
                logger.warning(
                    "FoundryGraph serving artifact unavailable; fall back to BigQuery."
                )
                return None

        _maybe_refresh()
        return _connection


def get_current_version() -> Optional[str]:
    """Get the current artifact version."""
    return _current_version
