# Vendor API to OTLP bridge example.
# Demonstrates polling a vendor API and converting JSON metrics to OTLP format.

import sys
import requests
import logging
from typing import Dict, Any

# Import MCA SDK (install via: pip install mca-sdk[vendor])
from mca_sdk.integrations import VendorBridge, SQLiteStateManager
from mca_sdk.utils.exceptions import ConfigurationError
from mca_sdk.config import MCAConfig

# Configure logger
logger = logging.getLogger(__name__)


class MockVendorBridge(VendorBridge):
    """Bridge for mock vendor API that polls and transforms metrics to OTLP."""

    def __init__(self, config: MCAConfig):
        """Initialize the vendor bridge."""
        vendor_api_url = config.vendor_api_url
        otlp_endpoint = config.collector_endpoint

        allow_insecure = config.allow_insecure_collector
        for url, name in [(vendor_api_url, "vendor_api_url"), (otlp_endpoint, "otlp_endpoint")]:
            is_local = self._is_local_endpoint(url)
            is_secure = url.startswith("https://")
            if not is_secure and not is_local and not allow_insecure:
                raise ConfigurationError(
                    f"{name} must use HTTPS for production endpoints. Got: {url}."
                )

        super().__init__(
            service_name="vendor-model",
            vendor_name="mock-vendor",
            endpoint=vendor_api_url,
            poll_interval=config.vendor_bridge_poll_interval,
            model_type="vendor",
            team_name=config.team_name or "integrations",
            max_iterations=config.vendor_bridge_max_iterations,
            retry_max_attempts=3,
            retry_base_delay=1.0,
            retry_max_delay=30.0,
            request_timeout=10.0,
            collector_endpoint=otlp_endpoint,
            metric_export_interval_ms=10000,
        )

        state_file = config.vendor_bridge_state_file
        self._state_manager = SQLiteStateManager(state_file)
        self._state_manager.connect()
        state = self._state_manager.load_state()
        self._cumulative_predictions = state.get("cumulative_predictions", 0)
        self._cumulative_errors = state.get("cumulative_errors", 0)

    def __enter__(self):
        """Enter the context manager, starting the bridge."""
        self.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Exit the context manager, closing the bridge."""
        self.close()

    @staticmethod
    def _is_local_endpoint(url: str) -> bool:
        """Check if URL is a local endpoint.

        Handles various localhost representations including:
        - localhost (IPv4 and IPv6)
        - 127.0.0.1 (IPv4 loopback)
        - ::1 (IPv6 loopback)
        - Docker internal service names (for container environments)
        """
        local_patterns = (
            "http://localhost",
            "http://127.0.0.1",
            "http://[::1]",
            "http://0.0.0.0",
            # Docker internal service names
            "http://vendor-api",
            "http://otel-collector",
            "http://mock-vendor",
        )
        return url.startswith(local_patterns)

    def close(self):
        """Stops the bridge and closes all resources gracefully."""
        logger.info("Closing bridge resources...")
        self.stop()
        if self._state_manager:
            self._state_manager.close()

    def fetch_metrics(self) -> Dict[str, Any]:
        """Fetch metrics from vendor API."""
        try:
            response = requests.get(self.endpoint, timeout=self.request_timeout)
            response.raise_for_status()

            if not response.content:
                raise ConfigurationError("Empty response from vendor API.")

            try:
                data = response.json()
            except requests.exceptions.JSONDecodeError as e:
                raise ConfigurationError(f"Invalid JSON response from vendor API: {e}")
            if "metrics" not in data:
                raise ConfigurationError(
                    f"Missing 'metrics' field in vendor API response. Got keys: {list(data.keys())}"
                )
            return data
        except requests.exceptions.Timeout:
            raise ConfigurationError(
                f"Request timeout after {self.request_timeout}s when polling {self.endpoint}"
            )
        except requests.exceptions.RequestException as e:
            raise ConfigurationError(f"Failed to fetch metrics from vendor API: {e}")

    def transform_metrics(self, data: Dict[str, Any]) -> Dict[str, Dict[str, float]]:
        """Transform vendor JSON to OTLP metrics, managing state via SQLite."""
        vendor_metrics = data.get("metrics", {})
        timestamp = data.get("timestamp", "unknown")

        predictions_delta = vendor_metrics.get("predictions_since_last_poll", 0)
        errors_delta = vendor_metrics.get("errors_since_last_poll", 0)

        with self._lock:
            # Calculate new cumulative values
            new_predictions = self._cumulative_predictions + predictions_delta
            new_errors = self._cumulative_errors + errors_delta

            # Save to persistent storage FIRST
            self._state_manager.save_state(
                {"cumulative_predictions": new_predictions, "cumulative_errors": new_errors}
            )

            # Only update in-memory state after successful save
            self._cumulative_predictions = new_predictions
            self._cumulative_errors = new_errors

            current_predictions = self._cumulative_predictions
            current_errors = self._cumulative_errors

        accuracy = vendor_metrics.get("accuracy", 0)
        f1_score = vendor_metrics.get("f1_score", 0)
        avg_latency_ms = vendor_metrics.get("avg_latency_ms", 0)

        gauges = {
            "vendor.accuracy_ratio": accuracy,
            "vendor.fscore_ratio": f1_score,
            "vendor.latency_seconds": avg_latency_ms / 1000.0,
        }
        counters = {
            "vendor.predictions_total": current_predictions,
            "vendor.errors_total": current_errors,
        }

        logger.debug(
            f"[{timestamp}] Exported - "
            f"accuracy={accuracy:.2f}, f1={f1_score:.2f}, "
            f"predictions_total={current_predictions}, "
            f"errors_total={current_errors}, "
            f"latency={avg_latency_ms}ms, "
            f"delta=(+{predictions_delta} predictions, +{errors_delta} errors)"
        )
        return {"gauges": gauges, "counters": counters}


if __name__ == "__main__":
    import time

    logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")

    # Load config from environment variables with MCA_ prefix
    config = MCAConfig.from_env()

    logger.info("=" * 60)
    logger.info("Vendor Model Bridge Example (SQLite Edition)")
    logger.info("=" * 60)
    logger.info(f"Vendor API:     {config.vendor_api_url}")
    logger.info(f"Collector:      {config.collector_endpoint}")
    logger.info("Poll Interval:  30 seconds")
    mode = (
        f"{config.vendor_bridge_max_iterations} iterations"
        if config.vendor_bridge_max_iterations
        else "continuous"
    )
    logger.info(f"Mode:           {mode}")
    persistence = config.vendor_bridge_state_file or "in-memory only"
    logger.info(f"State DB:       {persistence}")
    logger.info("=" * 60)

    error_occurred = False
    try:
        with MockVendorBridge(config) as bridge:
            logger.info("Bridge started. Press Ctrl+C to stop.")

            if config.vendor_bridge_max_iterations:
                if bridge.wait_for_completion():
                    stats = bridge.get_stats()
                    logger.info(
                        f"Completed {stats['poll_count']} polling cycles. Success: {stats['success_count']}, Errors: {stats['error_count']}"
                    )
            else:
                while bridge.is_running():
                    time.sleep(1)

    except KeyboardInterrupt:
        logger.info("Shutdown requested by user (Ctrl+C)")
    except Exception:
        logger.exception("An unexpected error occurred during bridge operation")
        error_occurred = True
    finally:
        logger.info("Shutdown complete.")

        # Exit with appropriate code
        exit_code = 1 if error_occurred else 0
        logger.info(f"Exiting with code {exit_code}")
        sys.exit(exit_code)
