"""Abstract log sink with local file and GCS implementations.

This module provides a pluggable logging infrastructure that writes structured
JSON log entries to local files and optionally syncs them to Google Cloud Storage.

Usage:
    from .logging_sinks import CompositeLogSink, FileLogSink, GCSLogSink

    # Create composite sink with local + GCS
    sinks = [FileLogSink(Path("/var/log/gateway.log"))]
    if gcs_bucket:
        sinks.append(GCSLogSink(bucket=gcs_bucket, prefix="logs/"))
    composite = CompositeLogSink(sinks)

    # Use with Python logging via SinkHandler
    handler = SinkHandler(composite)
    logger.addHandler(handler)
"""

from __future__ import annotations

import json
import logging
import queue
import threading
import time
from abc import ABC, abstractmethod
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Mapping, Sequence

_logger = logging.getLogger(__name__)


@dataclass(frozen=True, slots=True)
class LogEntry:
    """Structured log entry for cloud persistence."""

    timestamp_ms: int
    level: str
    logger_name: str
    message: str
    extra: Mapping[str, Any]


class LogSink(ABC):
    """Abstract sink for structured log entries."""

    @abstractmethod
    def write(self, entry: LogEntry) -> None:
        """Write a single log entry."""

    @abstractmethod
    def flush(self) -> None:
        """Flush any buffered entries."""

    def close(self) -> None:  # noqa: B027
        """Release resources. Default no-op."""
        pass


class FileLogSink(LogSink):
    """Local file sink writing JSON-lines format."""

    def __init__(self, path: Path) -> None:
        self._path = path
        self._path.parent.mkdir(parents=True, exist_ok=True)
        self._file = self._path.open("a", encoding="utf-8")
        self._lock = threading.Lock()

    def write(self, entry: LogEntry) -> None:
        line = json.dumps(
            {
                "ts": entry.timestamp_ms,
                "level": entry.level,
                "logger": entry.logger_name,
                "message": entry.message,
                "extra": dict(entry.extra),
            },
            ensure_ascii=False,
            default=str,
        )
        with self._lock:
            self._file.write(line + "\n")

    def flush(self) -> None:
        with self._lock:
            self._file.flush()

    def close(self) -> None:
        with self._lock:
            self._file.close()


class GCSLogSink(LogSink):
    """GCS sink with background batch uploads.

    Uses Application Default Credentials (ADC) for authentication. Configure via:
    - Local dev: `gcloud auth application-default login`
    - Cloud: workload identity or attached service account
    """

    def __init__(
        self,
        bucket: str,
        prefix: str,
        *,
        batch_size: int = 100,
        flush_interval_seconds: int = 60,
    ) -> None:
        self._bucket_name = bucket
        self._prefix = prefix.rstrip("/")
        self._batch_size = batch_size
        self._flush_interval = flush_interval_seconds

        # Lazy import to avoid hard dependency on google-cloud-storage
        try:
            from google.cloud import storage

            self._client = storage.Client()
            self._bucket = self._client.bucket(bucket)
        except ImportError as exc:
            raise ImportError(
                "google-cloud-storage package required for GCS log sink. "
                "Install with: pip install google-cloud-storage"
            ) from exc
        except Exception as exc:
            raise RuntimeError(f"Failed to initialize GCS client: {exc}") from exc

        self._buffer: queue.Queue[LogEntry] = queue.Queue()
        self._shutdown = threading.Event()
        self._flusher = threading.Thread(target=self._flush_loop, daemon=True)
        self._flusher.start()

    def write(self, entry: LogEntry) -> None:
        if self._shutdown.is_set():
            return
        self._buffer.put(entry)
        # Trigger immediate flush if batch size reached
        if self._buffer.qsize() >= self._batch_size:
            self._upload_buffered()

    def flush(self) -> None:
        self._upload_buffered()

    def close(self) -> None:
        self._shutdown.set()
        self._upload_buffered()
        self._flusher.join(timeout=5.0)

    def _flush_loop(self) -> None:
        """Background thread that periodically flushes buffered entries."""
        while not self._shutdown.is_set():
            self._shutdown.wait(timeout=self._flush_interval)
            if not self._shutdown.is_set():
                self._upload_buffered()

    def _drain_buffer(self) -> list[LogEntry]:
        """Non-blocking drain of all buffered entries."""
        entries: list[LogEntry] = []
        while True:
            try:
                entries.append(self._buffer.get_nowait())
            except queue.Empty:
                break
        return entries

    def _upload_buffered(self) -> None:
        """Upload buffered entries to GCS as a single JSON-lines blob."""
        entries = self._drain_buffer()
        if not entries:
            return

        timestamp = int(time.time() * 1000)
        blob_name = f"{self._prefix}/{timestamp}.jsonl"

        lines = []
        for entry in entries:
            lines.append(
                json.dumps(
                    {
                        "ts": entry.timestamp_ms,
                        "level": entry.level,
                        "logger": entry.logger_name,
                        "message": entry.message,
                        "extra": dict(entry.extra),
                    },
                    ensure_ascii=False,
                    default=str,
                )
            )

        content = "\n".join(lines)

        try:
            blob = self._bucket.blob(blob_name)
            blob.upload_from_string(content, content_type="application/jsonlines")
            _logger.debug(
                "Uploaded %d log entries to gs://%s/%s",
                len(entries),
                self._bucket_name,
                blob_name,
            )
        except Exception as exc:
            _logger.error("Failed to upload logs to GCS: %s", exc)


class CompositeLogSink(LogSink):
    """Fan-out sink that writes to multiple underlying sinks."""

    def __init__(self, sinks: Sequence[LogSink]) -> None:
        self._sinks = list(sinks)

    def write(self, entry: LogEntry) -> None:
        for sink in self._sinks:
            try:
                sink.write(entry)
            except Exception as exc:
                _logger.warning("Sink write failed: %s", exc)

    def flush(self) -> None:
        for sink in self._sinks:
            try:
                sink.flush()
            except Exception as exc:
                _logger.warning("Sink flush failed: %s", exc)

    def close(self) -> None:
        for sink in self._sinks:
            try:
                sink.close()
            except Exception as exc:
                _logger.warning("Sink close failed: %s", exc)


class SinkHandler(logging.Handler):
    """Logging handler that routes records to a LogSink."""

    def __init__(self, sink: LogSink) -> None:
        super().__init__()
        self._sink = sink

    def emit(self, record: logging.LogRecord) -> None:
        try:
            # Extract extra fields from the record
            extra: dict[str, Any] = {}
            # Common extra fields attached via logging calls
            for key in ("request_id", "org_id", "model", "provider", "stream"):
                if hasattr(record, key):
                    extra[key] = getattr(record, key)

            entry = LogEntry(
                timestamp_ms=int(record.created * 1000),
                level=record.levelname,
                logger_name=record.name,
                message=record.getMessage(),
                extra=extra,
            )
            self._sink.write(entry)
        except Exception:
            self.handleError(record)

    def flush(self) -> None:
        self._sink.flush()

    def close(self) -> None:
        super().close()
        self._sink.close()


__all__ = [
    "LogEntry",
    "LogSink",
    "FileLogSink",
    "GCSLogSink",
    "CompositeLogSink",
    "SinkHandler",
]
