"""
eaigins_sdk.observability
──────────────────────────
Observability integrations for the Eaigins SDK.

Answers question 8 (Observability) — integrates with:
    • OpenTelemetry (traces + spans)
    • Prometheus (metrics)
    • Structured JSON logging (compatible with Datadog, Grafana Loki, Splunk)
    • Webhook / HTTP export (generic SIEM integration)

Usage — OpenTelemetry::

    from eaigins_sdk.observability import OTelExporter
    from eaigins_sdk import ControlPlane
    from eaigins_sdk.policies import AML_POLICIES

    otel = OTelExporter(
        service_name    = "eaigins-aml-agent",
        otlp_endpoint   = "http://otel-collector:4317",  # your OTEL collector
    )
    cp = ControlPlane(policies=AML_POLICIES)
    cp.add_exporter(otel)

    # Every cp.evaluate() now emits a span to your OTEL backend

Usage — Prometheus::

    from eaigins_sdk.observability import PrometheusExporter

    prom = PrometheusExporter()
    cp.add_exporter(prom)

    # Exposes metrics at /metrics endpoint (requires prometheus_client)
    # Metrics:
    #   eaigins_decisions_total{decision="ALLOW|ESCALATE|BLOCK", domain="..."}
    #   eaigins_risk_score_histogram{domain="..."}
    #   eaigins_pipeline_latency_seconds{stage="..."}

Usage — Structured JSON log::

    from eaigins_sdk.observability import StructuredLogExporter
    import logging

    exporter = StructuredLogExporter(logger=logging.getLogger("eaigins"))
    cp.add_exporter(exporter)

Usage — Webhook / SIEM::

    from eaigins_sdk.observability import WebhookExporter

    exporter = WebhookExporter(
        url     = "https://your-siem.company.com/api/events",
        headers = {"Authorization": "Bearer YOUR_TOKEN"},
        on_block_only = True,   # only emit events for BLOCK decisions
    )
    cp.add_exporter(exporter)
"""

from __future__ import annotations

import json
import logging
import time
from abc import ABC, abstractmethod
from typing import Optional

from .models import GovernanceDecision, ExecutionDecision


# ── Base exporter interface ────────────────────────────────────────────────────

class BaseExporter(ABC):
    """Abstract base class for all Eaigins exporters."""

    @abstractmethod
    def export(self, decision: GovernanceDecision) -> None:
        """Called after every governance evaluation."""
        ...

    def on_start(self) -> None:
        """Called when the exporter is attached to a ControlPlane."""
        pass

    def flush(self) -> None:
        """Flush any buffered events. Called on shutdown."""
        pass


# ── Structured JSON log exporter ───────────────────────────────────────────────

class StructuredLogExporter(BaseExporter):
    """
    Emits structured JSON log lines for every governance decision.
    Compatible with Datadog Log Management, Grafana Loki, AWS CloudWatch,
    Splunk, and any system that ingests JSON logs.

    Each log line contains:
        timestamp, action_id, decision, risk_level, risk_score,
        tool_name, agent_id, violated_policies, reason
    """

    def __init__(
        self,
        logger:        Optional[logging.Logger] = None,
        level:         int                      = logging.INFO,
        block_level:   int                      = logging.WARNING,
        on_block_only: bool                     = False,
    ):
        self._logger       = logger or logging.getLogger("eaigins.decisions")
        self._level        = level
        self._block_level  = block_level
        self._on_block_only = on_block_only

    def export(self, decision: GovernanceDecision) -> None:
        if self._on_block_only and decision.decision == ExecutionDecision.ALLOW:
            return

        record = decision.to_dict()
        record["event"] = "eaigins.governance_decision"

        log_level = (self._block_level
                     if decision.decision != ExecutionDecision.ALLOW
                     else self._level)
        self._logger.log(log_level, json.dumps(record))


# ── OpenTelemetry exporter ─────────────────────────────────────────────────────

class OTelExporter(BaseExporter):
    """
    Exports governance decisions as OpenTelemetry traces.

    Each governance evaluation becomes a span with attributes:
        eaigins.decision, eaigins.risk_level, eaigins.risk_score,
        eaigins.tool_name, eaigins.agent_id, eaigins.violated_policies

    Requires: pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp

    The tracer is named "eaigins.control_plane".
    """

    def __init__(
        self,
        service_name:  str           = "eaigins",
        otlp_endpoint: Optional[str] = None,
        tracer_name:   str           = "eaigins.control_plane",
    ):
        self.service_name  = service_name
        self.otlp_endpoint = otlp_endpoint
        self.tracer_name   = tracer_name
        self._tracer       = None
        self._init_otel()

    def _init_otel(self):
        try:
            from opentelemetry import trace
            from opentelemetry.sdk.trace import TracerProvider
            from opentelemetry.sdk.resources import Resource

            resource = Resource.create({"service.name": self.service_name})
            provider = TracerProvider(resource=resource)

            if self.otlp_endpoint:
                try:
                    from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
                    from opentelemetry.sdk.trace.export import BatchSpanProcessor
                    exporter  = OTLPSpanExporter(endpoint=self.otlp_endpoint)
                    processor = BatchSpanProcessor(exporter)
                    provider.add_span_processor(processor)
                except ImportError:
                    logging.getLogger("eaigins.otel").warning(
                        "opentelemetry-exporter-otlp not installed. "
                        "Spans will not be exported. pip install opentelemetry-exporter-otlp"
                    )

            trace.set_tracer_provider(provider)
            self._tracer = trace.get_tracer(self.tracer_name)
        except ImportError:
            logging.getLogger("eaigins.otel").warning(
                "opentelemetry packages not installed. OTel export disabled. "
                "pip install opentelemetry-api opentelemetry-sdk"
            )

    def export(self, decision: GovernanceDecision) -> None:
        if self._tracer is None:
            return
        from opentelemetry import trace
        with self._tracer.start_as_current_span("eaigins.evaluate") as span:
            span.set_attribute("eaigins.action_id",         decision.action_id)
            span.set_attribute("eaigins.decision",          decision.decision.value)
            span.set_attribute("eaigins.risk_level",        decision.risk_level.value)
            span.set_attribute("eaigins.risk_score",        decision.risk_score)
            span.set_attribute("eaigins.reason",            decision.reason[:200])
            if decision.firewall_result:
                span.set_attribute("eaigins.firewall.passed",      decision.firewall_result.passed)
                span.set_attribute("eaigins.firewall.threat_type", decision.firewall_result.threat_type.value)
                span.set_attribute("eaigins.firewall.score",       decision.firewall_result.threat_score)
            if decision.policy_result:
                span.set_attribute("eaigins.policy.passed",   decision.policy_result.passed)
                span.set_attribute("eaigins.policy.violated", len(decision.policy_result.violated_policies))
            if decision.decision != ExecutionDecision.ALLOW:
                span.set_status(trace.Status(trace.StatusCode.ERROR, decision.reason[:200]))


# ── Prometheus exporter ────────────────────────────────────────────────────────

class PrometheusExporter(BaseExporter):
    """
    Exposes Eaigins governance metrics for Prometheus scraping.

    Metrics:
        eaigins_decisions_total{decision, risk_level}   Counter
        eaigins_risk_score                              Histogram
        eaigins_firewall_threats_total{threat_type}     Counter

    Requires: pip install prometheus_client

    To serve metrics:
        from prometheus_client import start_http_server
        start_http_server(8000)   # metrics at http://localhost:8000/metrics
    """

    def __init__(self, namespace: str = "eaigins"):
        self._ns = namespace
        self._counters   = {}
        self._histograms = {}
        self._init_metrics()

    def _init_metrics(self):
        try:
            from prometheus_client import Counter, Histogram
            self._decision_counter = Counter(
                f"{self._ns}_decisions_total",
                "Total governance decisions",
                ["decision", "risk_level"],
            )
            self._risk_histogram = Histogram(
                f"{self._ns}_risk_score",
                "Distribution of risk scores",
                buckets=[0.1, 0.2, 0.35, 0.5, 0.65, 0.75, 0.85, 0.95, 1.0],
            )
            self._threat_counter = Counter(
                f"{self._ns}_firewall_threats_total",
                "Firewall threats detected",
                ["threat_type"],
            )
            self._available = True
        except ImportError:
            logging.getLogger("eaigins.prometheus").warning(
                "prometheus_client not installed. Metrics export disabled. "
                "pip install prometheus_client"
            )
            self._available = False

    def export(self, decision: GovernanceDecision) -> None:
        if not self._available:
            return
        self._decision_counter.labels(
            decision   = decision.decision.value,
            risk_level = decision.risk_level.value,
        ).inc()
        self._risk_histogram.observe(decision.risk_score)
        if decision.firewall_result and not decision.firewall_result.passed:
            self._threat_counter.labels(
                threat_type=decision.firewall_result.threat_type.value
            ).inc()


# ── Webhook / SIEM exporter ────────────────────────────────────────────────────

class WebhookExporter(BaseExporter):
    """
    POSTs governance decisions to a webhook endpoint (SIEM, Slack, PagerDuty, etc.).

    Requires: pip install httpx  (or falls back to urllib)

    Compatible with:
        - Datadog Events API
        - Splunk HEC
        - Elastic Common Schema HTTP endpoint
        - Slack incoming webhooks
        - PagerDuty Events API v2
        - Any custom SIEM HTTP intake
    """

    def __init__(
        self,
        url:           str,
        headers:       Optional[dict] = None,
        on_block_only: bool           = False,
        timeout:       float          = 5.0,
        async_mode:    bool           = False,
    ):
        self._url          = url
        self._headers      = {"Content-Type": "application/json", **(headers or {})}
        self._on_block_only = on_block_only
        self._timeout      = timeout
        self._async_mode   = async_mode
        self._logger       = logging.getLogger("eaigins.webhook")

    def export(self, decision: GovernanceDecision) -> None:
        if self._on_block_only and decision.decision == ExecutionDecision.ALLOW:
            return
        payload = decision.to_dict()
        payload["source"] = "eaigins-sdk"
        self._send(payload)

    def _send(self, payload: dict) -> None:
        body = json.dumps(payload).encode()
        try:
            try:
                import httpx
                httpx.post(self._url, content=body, headers=self._headers,
                           timeout=self._timeout)
            except ImportError:
                import urllib.request
                req = urllib.request.Request(
                    self._url, data=body, headers=self._headers, method="POST"
                )
                urllib.request.urlopen(req, timeout=self._timeout)
        except Exception as e:
            self._logger.warning(f"Webhook export failed: {e} — url={self._url!r}")


# ── In-memory buffer exporter (for testing / batching) ───────────────────────

class BufferedExporter(BaseExporter):
    """
    Buffers all decisions in memory. Useful for testing and batch export.

    Usage::

        exporter = BufferedExporter()
        cp.add_exporter(exporter)

        # ... run evaluations ...

        decisions = exporter.get_decisions()
        # flush to your own system
    """

    def __init__(self):
        self._buffer: list[GovernanceDecision] = []

    def export(self, decision: GovernanceDecision) -> None:
        self._buffer.append(decision)

    def get_decisions(self) -> list[GovernanceDecision]:
        return list(self._buffer)

    def get_audit_trail(self) -> list[dict]:
        return [d.to_dict() for d in self._buffer]

    def flush(self) -> None:
        self._buffer.clear()

    def __len__(self) -> int:
        return len(self._buffer)
