#!/usr/bin/env python3
"""
Marziel AI — Maritime Connector SDK (Enterprise)

Pluggable connector system for integrating with ANY third-party
maritime service. Enterprise users configure connectors via
~/.marziel/connectors.json or environment variables.

Built-in connector support:
  - MarineTraffic API
  - VesselFinder API
  - Spire Maritime AIS
  - Datalastic Vessel API
  - OpenWeather Marine
  - World Port Source
  - SeaRoutes API
  - Custom REST API (any URL)

Usage:
  from marziel.connectors import ConnectorManager
  mgr = ConnectorManager()
  mgr.query("vessels", params={"mmsi": "123456789"})
"""
import json
import logging
import os
import urllib.request
from datetime import datetime
from pathlib import Path

logger = logging.getLogger("connectors")

CONNECTORS_FILE = Path.home() / ".marziel" / "connectors.json"

# ══════════════════════════════════════════════════════════
# BASE CONNECTOR
# ══════════════════════════════════════════════════════════

class MaritimeConnector:
    """Base class for all maritime service connectors."""

    name = "base"
    description = "Base maritime connector"
    docs_url = ""
    requires_key = True

    def __init__(self, config: dict):
        self.api_key = config.get("api_key", "")
        self.base_url = config.get("base_url", "")
        self.enabled = config.get("enabled", True)
        self.config = config

    def _request(self, url: str, headers: dict = None, timeout: int = 15) -> dict | list | None:
        """Make an HTTP request with standard error handling."""
        hdrs = {
            "Accept": "application/json",
            "User-Agent": "Marziel-AI/1.0 Enterprise",
        }
        if headers:
            hdrs.update(headers)
        try:
            req = urllib.request.Request(url, headers=hdrs)
            resp = urllib.request.urlopen(req, timeout=timeout)
            return json.loads(resp.read().decode())
        except Exception as e:
            logger.warning(f"[{self.name}] Request failed: {e}")
            return None

    def test_connection(self) -> dict:
        """Test if the connector is reachable and authenticated."""
        return {"connector": self.name, "status": "not_implemented"}

    def get_vessels(self, **kwargs) -> dict:
        return {"error": f"{self.name}: get_vessels not supported"}

    def get_vessel_by_mmsi(self, mmsi: str) -> dict:
        return {"error": f"{self.name}: get_vessel_by_mmsi not supported"}

    def get_vessel_by_imo(self, imo: str) -> dict:
        return {"error": f"{self.name}: get_vessel_by_imo not supported"}

    def get_vessel_track(self, identifier: str, days: int = 7) -> dict:
        return {"error": f"{self.name}: get_vessel_track not supported"}

    def get_port_info(self, port_code: str) -> dict:
        return {"error": f"{self.name}: get_port_info not supported"}

    def get_weather(self, lat: float, lon: float) -> dict:
        return {"error": f"{self.name}: get_weather not supported"}

    def get_routes(self, origin: str, destination: str) -> dict:
        return {"error": f"{self.name}: get_routes not supported"}

    def search(self, query: str) -> dict:
        return {"error": f"{self.name}: search not supported"}


# ══════════════════════════════════════════════════════════
# MARINETRAFFIC CONNECTOR
# ══════════════════════════════════════════════════════════

class MarineTrafficConnector(MaritimeConnector):
    """
    MarineTraffic API — world's largest ship tracking platform.
    API docs: https://www.marinetraffic.com/en/ais-api-services
    Required: API key from MarineTraffic subscription.
    """

    name = "marinetraffic"
    description = "MarineTraffic AIS — Global vessel tracking (marinetraffic.com)"
    docs_url = "https://www.marinetraffic.com/en/ais-api-services"

    def __init__(self, config: dict):
        super().__init__(config)
        self.base_url = config.get("base_url", "https://services.marinetraffic.com/api/exportvessel/v:5")

    def get_vessel_by_mmsi(self, mmsi: str) -> dict:
        url = f"{self.base_url}/{self.api_key}/protocol:jsono/mmsi:{mmsi}"
        data = self._request(url)
        return {"source": "MarineTraffic", "vessel": data} if data else {"error": "Vessel not found"}

    def get_vessel_by_imo(self, imo: str) -> dict:
        url = f"{self.base_url}/{self.api_key}/protocol:jsono/imo:{imo}"
        data = self._request(url)
        return {"source": "MarineTraffic", "vessel": data} if data else {"error": "Vessel not found"}

    def get_vessel_track(self, identifier: str, days: int = 7) -> dict:
        url = (
            f"https://services.marinetraffic.com/api/exportvesseltrack/v:2"
            f"/{self.api_key}/protocol:jsono/mmsi:{identifier}/days:{days}"
        )
        data = self._request(url)
        return {"source": "MarineTraffic", "track": data} if data else {"error": "Track not found"}

    def test_connection(self) -> dict:
        if not self.api_key:
            return {"connector": self.name, "status": "error", "message": "No API key configured"}
        return {"connector": self.name, "status": "ok" if self.api_key else "no_key"}


# ══════════════════════════════════════════════════════════
# VESSELFINDER CONNECTOR
# ══════════════════════════════════════════════════════════

class VesselFinderConnector(MaritimeConnector):
    """
    VesselFinder API — AIS vessel tracking.
    API docs: https://api.vesselfinder.com/docs
    Required: API key from VesselFinder.
    """

    name = "vesselfinder"
    description = "VesselFinder — AIS data & vessel info (vesselfinder.com)"
    docs_url = "https://api.vesselfinder.com/docs"

    def __init__(self, config: dict):
        super().__init__(config)
        self.base_url = config.get("base_url", "https://api.vesselfinder.com")

    def get_vessels(self, **kwargs) -> dict:
        lat_min = kwargs.get("lat_min", -90)
        lat_max = kwargs.get("lat_max", 90)
        lon_min = kwargs.get("lon_min", -180)
        lon_max = kwargs.get("lon_max", 180)
        url = (
            f"{self.base_url}/vessels"
            f"?userkey={self.api_key}"
            f"&latmin={lat_min}&latmax={lat_max}&lonmin={lon_min}&lonmax={lon_max}"
        )
        data = self._request(url)
        return {"source": "VesselFinder", "vessels": data} if data else {"error": "No vessels found"}

    def get_vessel_by_mmsi(self, mmsi: str) -> dict:
        url = f"{self.base_url}/vessels?userkey={self.api_key}&mmsi={mmsi}"
        data = self._request(url)
        return {"source": "VesselFinder", "vessel": data} if data else {"error": "Vessel not found"}

    def get_vessel_by_imo(self, imo: str) -> dict:
        url = f"{self.base_url}/vessels?userkey={self.api_key}&imo={imo}"
        data = self._request(url)
        return {"source": "VesselFinder", "vessel": data} if data else {"error": "Vessel not found"}

    def test_connection(self) -> dict:
        if not self.api_key:
            return {"connector": self.name, "status": "error", "message": "No API key"}
        return {"connector": self.name, "status": "ok"}


# ══════════════════════════════════════════════════════════
# SPIRE MARITIME CONNECTOR
# ══════════════════════════════════════════════════════════

class SpireConnector(MaritimeConnector):
    """
    Spire Maritime — Satellite AIS & vessel intelligence.
    API docs: https://documentation.spire.com/maritime/reference
    Required: Bearer token from Spire.
    """

    name = "spire"
    description = "Spire Maritime — Satellite AIS intelligence (spire.com)"
    docs_url = "https://documentation.spire.com/maritime/reference"

    def __init__(self, config: dict):
        super().__init__(config)
        self.base_url = config.get("base_url", "https://api.spire.com/v2")

    def _auth_headers(self):
        return {"Authorization": f"Bearer {self.api_key}"}

    def get_vessels(self, **kwargs) -> dict:
        url = f"{self.base_url}/vessels/latest"
        params = []
        if kwargs.get("mmsi"):
            params.append(f"mmsi={kwargs['mmsi']}")
        if kwargs.get("imo"):
            params.append(f"imo={kwargs['imo']}")
        if kwargs.get("flag"):
            params.append(f"flag={kwargs['flag']}")
        if params:
            url += "?" + "&".join(params)
        data = self._request(url, headers=self._auth_headers())
        return {"source": "Spire Maritime", "data": data} if data else {"error": "No data"}

    def get_vessel_by_mmsi(self, mmsi: str) -> dict:
        return self.get_vessels(mmsi=mmsi)

    def get_vessel_by_imo(self, imo: str) -> dict:
        return self.get_vessels(imo=imo)

    def test_connection(self) -> dict:
        if not self.api_key:
            return {"connector": self.name, "status": "error", "message": "No Bearer token"}
        return {"connector": self.name, "status": "ok"}


# ══════════════════════════════════════════════════════════
# DATALASTIC CONNECTOR
# ══════════════════════════════════════════════════════════

class DatalasticConnector(MaritimeConnector):
    """
    Datalastic — Vessel tracking & port data.
    API docs: https://datalastic.com/api-reference
    Required: API key from Datalastic.
    """

    name = "datalastic"
    description = "Datalastic — Vessel & port intelligence (datalastic.com)"
    docs_url = "https://datalastic.com/api-reference"

    def __init__(self, config: dict):
        super().__init__(config)
        self.base_url = config.get("base_url", "https://api.datalastic.com/api/v0")

    def get_vessel_by_mmsi(self, mmsi: str) -> dict:
        url = f"{self.base_url}/vessel?api-key={self.api_key}&mmsi={mmsi}"
        data = self._request(url)
        return {"source": "Datalastic", "vessel": data} if data else {"error": "Not found"}

    def get_vessel_by_imo(self, imo: str) -> dict:
        url = f"{self.base_url}/vessel?api-key={self.api_key}&imo={imo}"
        data = self._request(url)
        return {"source": "Datalastic", "vessel": data} if data else {"error": "Not found"}

    def get_port_info(self, port_code: str) -> dict:
        url = f"{self.base_url}/port?api-key={self.api_key}&port-code={port_code}"
        data = self._request(url)
        return {"source": "Datalastic", "port": data} if data else {"error": "Not found"}

    def test_connection(self) -> dict:
        if not self.api_key:
            return {"connector": self.name, "status": "error", "message": "No API key"}
        return {"connector": self.name, "status": "ok"}


# ══════════════════════════════════════════════════════════
# OPENWEATHER MARINE CONNECTOR
# ══════════════════════════════════════════════════════════

class OpenWeatherConnector(MaritimeConnector):
    """
    OpenWeather — Marine weather forecasts.
    API docs: https://openweathermap.org/api
    Required: API key from OpenWeather (free tier available).
    """

    name = "openweather"
    description = "OpenWeather — Marine weather & forecasts (openweathermap.org)"
    docs_url = "https://openweathermap.org/api"
    requires_key = True

    def __init__(self, config: dict):
        super().__init__(config)
        self.base_url = config.get("base_url", "https://api.openweathermap.org/data/2.5")

    def get_weather(self, lat: float, lon: float) -> dict:
        url = f"{self.base_url}/weather?lat={lat}&lon={lon}&appid={self.api_key}&units=metric"
        data = self._request(url)
        if not data:
            return {"error": "Weather unavailable"}
        return {
            "source": "OpenWeather",
            "position": {"lat": lat, "lon": lon},
            "temperature_c": data.get("main", {}).get("temp"),
            "humidity": data.get("main", {}).get("humidity"),
            "wind_speed_ms": data.get("wind", {}).get("speed"),
            "wind_deg": data.get("wind", {}).get("deg"),
            "description": data.get("weather", [{}])[0].get("description", ""),
            "visibility_m": data.get("visibility"),
            "pressure_hpa": data.get("main", {}).get("pressure"),
            "sea_level_hpa": data.get("main", {}).get("sea_level"),
        }

    def test_connection(self) -> dict:
        if not self.api_key:
            return {"connector": self.name, "status": "error", "message": "No API key"}
        # Test with London coordinates
        result = self.get_weather(51.5, -0.1)
        return {"connector": self.name, "status": "ok" if "temperature_c" in result else "error"}


# ══════════════════════════════════════════════════════════
# CUSTOM REST API CONNECTOR
# ══════════════════════════════════════════════════════════

class CustomAPIConnector(MaritimeConnector):
    """
    Custom REST API — Connect Marziel to any maritime service.
    Configure base_url, headers, and endpoint mappings.

    Config example:
    {
        "type": "custom",
        "name": "my_fleet_api",
        "base_url": "https://api.myfleet.com/v1",
        "api_key": "my-secret-key",
        "auth_header": "Authorization",
        "auth_prefix": "Bearer",
        "endpoints": {
            "vessels": "/vessels",
            "vessel_detail": "/vessels/{id}",
            "positions": "/tracking/positions",
            "voyages": "/voyages",
            "weather": "/weather?lat={lat}&lon={lon}"
        }
    }
    """

    name = "custom"
    description = "Custom REST API — Connect to any maritime service"
    requires_key = False

    def __init__(self, config: dict):
        super().__init__(config)
        self.name = config.get("name", "custom")
        self.auth_header = config.get("auth_header", "Authorization")
        self.auth_prefix = config.get("auth_prefix", "Bearer")
        self.endpoints = config.get("endpoints", {})

    def _auth_headers(self):
        if self.api_key:
            return {self.auth_header: f"{self.auth_prefix} {self.api_key}".strip()}
        return {}

    def _call_endpoint(self, endpoint_key: str, **kwargs) -> dict | None:
        path = self.endpoints.get(endpoint_key)
        if not path:
            return None
        # Substitute {param} placeholders
        for k, v in kwargs.items():
            path = path.replace(f"{{{k}}}", str(v))
        url = f"{self.base_url}{path}"
        return self._request(url, headers=self._auth_headers())

    def get_vessels(self, **kwargs) -> dict:
        data = self._call_endpoint("vessels", **kwargs)
        return {"source": self.name, "data": data} if data else {"error": f"No data from {self.name}"}

    def get_vessel_by_mmsi(self, mmsi: str) -> dict:
        data = self._call_endpoint("vessel_detail", id=mmsi, mmsi=mmsi)
        return {"source": self.name, "vessel": data} if data else {"error": "Not found"}

    def get_vessel_track(self, identifier: str, days: int = 7) -> dict:
        data = self._call_endpoint("positions", id=identifier, mmsi=identifier, days=days)
        return {"source": self.name, "track": data} if data else {"error": "No track data"}

    def get_weather(self, lat: float, lon: float) -> dict:
        data = self._call_endpoint("weather", lat=lat, lon=lon)
        return {"source": self.name, "weather": data} if data else {"error": "No weather"}

    def search(self, query: str) -> dict:
        data = self._call_endpoint("search", query=query, q=query)
        return {"source": self.name, "results": data} if data else {"error": "No results"}

    def test_connection(self) -> dict:
        if not self.base_url:
            return {"connector": self.name, "status": "error", "message": "No base_url configured"}
        return {"connector": self.name, "status": "ok", "endpoints": list(self.endpoints.keys())}


# ══════════════════════════════════════════════════════════
# CONNECTOR REGISTRY
# ══════════════════════════════════════════════════════════

CONNECTOR_TYPES = {
    "marinetraffic": MarineTrafficConnector,
    "vesselfinder": VesselFinderConnector,
    "spire": SpireConnector,
    "datalastic": DatalasticConnector,
    "openweather": OpenWeatherConnector,
    "custom": CustomAPIConnector,
}


# ══════════════════════════════════════════════════════════
# CONNECTOR MANAGER
# ══════════════════════════════════════════════════════════

class ConnectorManager:
    """
    Manages all configured maritime connectors.

    Connectors are loaded from ~/.marziel/connectors.json:
    {
        "connectors": [
            {
                "type": "marinetraffic",
                "api_key": "YOUR_KEY",
                "enabled": true
            },
            {
                "type": "openweather",
                "api_key": "YOUR_KEY"
            },
            {
                "type": "custom",
                "name": "my_fleet",
                "base_url": "https://api.myfleet.com/v1",
                "api_key": "secret",
                "endpoints": {"vessels": "/ships", "voyages": "/trips"}
            }
        ]
    }
    """

    def __init__(self):
        self.connectors: list[MaritimeConnector] = []
        self._load_config()

    def _load_config(self):
        """Load connector configurations from file and env vars."""
        # Load from file
        if CONNECTORS_FILE.exists():
            try:
                with open(CONNECTORS_FILE) as f:
                    config = json.load(f)
                for conn_cfg in config.get("connectors", []):
                    conn_type = conn_cfg.get("type", "custom")
                    cls = CONNECTOR_TYPES.get(conn_type, CustomAPIConnector)
                    connector = cls(conn_cfg)
                    if connector.enabled:
                        self.connectors.append(connector)
                        logger.info(f"Loaded connector: {connector.name}")
            except Exception as e:
                logger.warning(f"Failed to load connectors.json: {e}")

        # Load from environment variables
        env_connectors = {
            "MARINETRAFFIC_API_KEY": "marinetraffic",
            "VESSELFINDER_API_KEY": "vesselfinder",
            "SPIRE_API_KEY": "spire",
            "DATALASTIC_API_KEY": "datalastic",
            "OPENWEATHER_API_KEY": "openweather",
        }
        for env_key, conn_type in env_connectors.items():
            api_key = os.environ.get(env_key)
            if api_key and not any(c.name == conn_type for c in self.connectors):
                cls = CONNECTOR_TYPES[conn_type]
                connector = cls({"api_key": api_key})
                self.connectors.append(connector)
                logger.info(f"Loaded connector from env: {connector.name}")

    def list_connectors(self) -> list:
        """List all available connector types and their status."""
        available = []
        for type_name, cls in CONNECTOR_TYPES.items():
            instance = cls({})
            active = any(c.name == type_name for c in self.connectors)
            available.append({
                "type": type_name,
                "description": instance.description,
                "docs": instance.docs_url,
                "requires_key": instance.requires_key,
                "active": active,
            })
        return available

    def get_active_connectors(self) -> list:
        """List actively configured connectors."""
        return [{"name": c.name, "type": c.__class__.__name__} for c in self.connectors]

    def test_all(self) -> list:
        """Test all configured connectors."""
        results = []
        for c in self.connectors:
            results.append(c.test_connection())
        return results

    # ── Unified Query Interface ──

    def query_vessels(self, **kwargs) -> list:
        """Query all connectors for vessel data, merge results."""
        results = []
        for c in self.connectors:
            try:
                data = c.get_vessels(**kwargs)
                if not data.get("error"):
                    results.append(data)
            except Exception as e:
                logger.warning(f"[{c.name}] query_vessels failed: {e}")
        return results

    def query_vessel_by_mmsi(self, mmsi: str) -> list:
        """Query all connectors for a specific vessel by MMSI."""
        results = []
        for c in self.connectors:
            try:
                data = c.get_vessel_by_mmsi(mmsi)
                if not data.get("error"):
                    results.append(data)
            except Exception as e:
                logger.warning(f"[{c.name}] mmsi query failed: {e}")
        return results

    def query_vessel_by_imo(self, imo: str) -> list:
        """Query all connectors for a specific vessel by IMO."""
        results = []
        for c in self.connectors:
            try:
                data = c.get_vessel_by_imo(imo)
                if not data.get("error"):
                    results.append(data)
            except Exception as e:
                logger.warning(f"[{c.name}] imo query failed: {e}")
        return results

    def query_weather(self, lat: float, lon: float) -> list:
        """Query all weather-capable connectors."""
        results = []
        for c in self.connectors:
            try:
                data = c.get_weather(lat, lon)
                if not data.get("error"):
                    results.append(data)
            except Exception as e:
                logger.warning(f"[{c.name}] weather query failed: {e}")
        return results

    def query_port(self, port_code: str) -> list:
        """Query all connectors for port info."""
        results = []
        for c in self.connectors:
            try:
                data = c.get_port_info(port_code)
                if not data.get("error"):
                    results.append(data)
            except Exception as e:
                logger.warning(f"[{c.name}] port query failed: {e}")
        return results

    def query(self, action: str, **kwargs) -> dict:
        """
        Universal query interface — route any action to all connectors.

        Actions: vessels, vessel_mmsi, vessel_imo, vessel_track,
                 weather, port, search, test, list
        """
        ts = datetime.utcnow().isoformat() + "Z"

        if action == "list":
            return {"connectors": self.list_connectors(), "active": self.get_active_connectors()}
        elif action == "test":
            return {"results": self.test_all()}
        elif action == "vessels":
            return {"timestamp": ts, "results": self.query_vessels(**kwargs)}
        elif action == "vessel_mmsi":
            return {"timestamp": ts, "results": self.query_vessel_by_mmsi(kwargs.get("mmsi", ""))}
        elif action == "vessel_imo":
            return {"timestamp": ts, "results": self.query_vessel_by_imo(kwargs.get("imo", ""))}
        elif action == "vessel_track":
            results = []
            for c in self.connectors:
                try:
                    data = c.get_vessel_track(kwargs.get("id", ""), kwargs.get("days", 7))
                    if not data.get("error"):
                        results.append(data)
                except:
                    pass
            return {"timestamp": ts, "results": results}
        elif action == "weather":
            return {"timestamp": ts, "results": self.query_weather(kwargs.get("lat", 0), kwargs.get("lon", 0))}
        elif action == "port":
            return {"timestamp": ts, "results": self.query_port(kwargs.get("code", ""))}
        elif action == "search":
            results = []
            for c in self.connectors:
                try:
                    data = c.search(kwargs.get("query", ""))
                    if not data.get("error"):
                        results.append(data)
                except:
                    pass
            return {"timestamp": ts, "results": results}
        else:
            return {"error": f"Unknown action: {action}"}


# ── Module-level singleton ──
_manager: ConnectorManager | None = None


def get_connector_manager() -> ConnectorManager:
    """Get or create the singleton ConnectorManager."""
    global _manager
    if _manager is None:
        _manager = ConnectorManager()
    return _manager
