"""
Sync logistics node to Optovia Maps graph.
1. Fetch node data from Odoo (with transport_types)
2. Save node to ArangoDB
3. Process by transport type:
   - Automotive: isochrone + neighbors + GraphHopper routes
   - Railway: all rail stations + OpenRailRouting distances
"""
import os
import json
from datetime import datetime, timedelta

# Load secrets from Infisical first
import config  # noqa: F401

import httpx
from arango import ArangoClient
from prefect import flow, task, get_run_logger
from prefect.cache_policies import INPUTS


# Environment (loaded from Infisical via config.py)
ODOO_URL = os.environ.get("ODOO_URL", "http://odoo:8069")
GRAPHHOPPER_URL = os.environ.get("GRAPHHOPPER_URL", "https://graphhopper.optovia.ru")
RAIL_ROUTING_URL = os.environ.get("RAIL_ROUTING_URL", "https://openrailrouting.optovia.ru")
ARANGODB_URL = os.environ.get("ARANGODB_URL", "http://217.216.32.39:32844")
ARANGODB_DATABASE = os.environ.get("ARANGODB_DATABASE", "optovia_maps")
ARANGODB_PASSWORD = os.environ.get("ARANGODB_PASSWORD", "")


def get_db():
    """Get ArangoDB connection."""
    client = ArangoClient(hosts=ARANGODB_URL)
    return client.db(ARANGODB_DATABASE, username="root", password=ARANGODB_PASSWORD)


@task(retries=3, retry_delay_seconds=10)
def fetch_node(node_uuid: str) -> dict:
    """Fetch node data from Odoo (for logistics nodes)."""
    logger = get_run_logger()
    url = f"{ODOO_URL}/fastapi/logistics/nodes/{node_uuid}"

    with httpx.Client(timeout=30) as client:
        response = client.get(url)
        response.raise_for_status()
        node_data = response.json()

    # Odoo nodes are always logistics type
    node_data['node_type'] = 'logistics'

    logger.info(f"Fetched node: {node_data['name']} ({node_uuid})")
    return node_data


@task
def save_node(node_data: dict) -> str:
    """Save node to ArangoDB."""
    logger = get_run_logger()
    db = get_db()

    if not db.has_collection("nodes"):
        db.create_collection("nodes")

    nodes = db.collection("nodes")

    doc = {
        "_key": node_data["uuid"],
        "name": node_data["name"],
        "latitude": node_data["latitude"],
        "longitude": node_data["longitude"],
        "country": node_data.get("country"),
        "country_code": node_data.get("country_code"),
        "transport_types": node_data.get("transport_types", []),
        "node_type": node_data.get("node_type", "logistics"),  # logistics | buyer | seller
        "team_uuid": node_data.get("team_uuid"),  # Only for buyer/seller nodes
        "synced_at": datetime.utcnow().isoformat(),
    }

    if nodes.has(node_data["uuid"]):
        nodes.update(doc)
        logger.info(f"Updated node: {node_data['uuid']} (type={doc['node_type']})")
    else:
        nodes.insert(doc)
        logger.info(f"Inserted node: {node_data['uuid']} (type={doc['node_type']})")

    return node_data["uuid"]


@task(
    cache_policy=INPUTS,
    cache_expiration=timedelta(days=7),
    retries=2,
    retry_delay_seconds=60,
    timeout_seconds=1800,
)
def calculate_isochrone(lat: float, lon: float, node_uuid: str) -> dict | None:
    """Calculate isochrone via GraphHopper (20 hours car)."""
    logger = get_run_logger()
    url = f"{GRAPHHOPPER_URL}/isochrone"
    params = {
        "point": f"{lat},{lon}",
        "time_limit": 72000,  # 20 hours in seconds
        "profile": "car",
    }

    with httpx.Client(timeout=httpx.Timeout(60.0, read=1800.0)) as client:
        response = client.get(url, params=params)
        response.raise_for_status()
        data = response.json()

    if "polygons" in data and len(data["polygons"]) > 0:
        geometry = data["polygons"][0].get("geometry")
        if geometry:
            logger.info(f"Calculated isochrone for {node_uuid}")
            return geometry

    logger.warning(f"No isochrone polygon for {node_uuid}")
    return None


@task
def save_isochrone(node_uuid: str, geometry: dict, duration_minutes: int = 1200) -> bool:
    """Save isochrone to ArangoDB."""
    logger = get_run_logger()
    db = get_db()

    if not db.has_collection("isochrones"):
        col = db.create_collection("isochrones")
        col.add_geo_index(fields=["geometry"], geo_json=True)

    isochrones = db.collection("isochrones")

    doc = {
        "_key": node_uuid,
        "node_uuid": node_uuid,
        "duration_minutes": duration_minutes,
        "profile": "car",
        "geometry": geometry,
        "calculated_at": datetime.utcnow().isoformat(),
    }

    if isochrones.has(node_uuid):
        isochrones.update(doc)
        logger.info(f"Updated isochrone for {node_uuid}")
    else:
        isochrones.insert(doc)
        logger.info(f"Created isochrone for {node_uuid}")

    return True


@task
def find_auto_neighbors(node_uuid: str) -> list[dict]:
    """
    Find nodes with 'auto' transport type within isochrone.

    Filtering rules based on node_type:
    - logistics: connects to logistics only
    - seller: connects to logistics and buyer
    - buyer: connects to logistics only
    """
    logger = get_run_logger()
    db = get_db()

    # Get source node type
    source_node = db.collection("nodes").get(node_uuid)
    if not source_node:
        logger.warning(f"Node {node_uuid} not found")
        return []

    source_type = source_node.get("node_type", "logistics")

    isochrone = db.collection("isochrones").get(node_uuid)
    if not isochrone or "geometry" not in isochrone:
        logger.warning(f"No isochrone for {node_uuid}")
        return []

    geometry = isochrone["geometry"]

    # Define allowed neighbor types based on source type
    if source_type == "logistics":
        allowed_types = ["logistics"]
    elif source_type == "seller":
        allowed_types = ["logistics", "buyer"]
    elif source_type == "buyer":
        allowed_types = ["logistics"]
    else:
        allowed_types = ["logistics"]  # Default fallback

    aql = """
    FOR node IN nodes
      FILTER node._key != @node_uuid
      FILTER 'auto' IN node.transport_types
      FILTER node.node_type IN @allowed_types
      FILTER GEO_CONTAINS(@geometry, [node.longitude, node.latitude])
      RETURN {
        uuid: node._key,
        name: node.name,
        latitude: node.latitude,
        longitude: node.longitude
      }
    """

    cursor = db.aql.execute(
        aql,
        bind_vars={
            "node_uuid": node_uuid,
            "geometry": geometry,
            "allowed_types": allowed_types,
        }
    )
    neighbors = list(cursor)
    logger.info(
        f"Found {len(neighbors)} auto neighbors for {node_uuid} "
        f"(type={source_type}, allowed={allowed_types})"
    )
    return neighbors


@task(retries=2, retry_delay_seconds=10, timeout_seconds=60)
def calculate_auto_route(from_lat: float, from_lon: float, to_lat: float, to_lon: float) -> dict | None:
    """Calculate route via GraphHopper."""
    url = f"{GRAPHHOPPER_URL}/route"
    params = {
        "point": [f"{from_lat},{from_lon}", f"{to_lat},{to_lon}"],
        "profile": "car",
        "instructions": "false",
        "calc_points": "false",
    }

    try:
        with httpx.Client(timeout=30) as client:
            response = client.get(url, params=params)
            if response.status_code != 200:
                return None
            data = response.json()

        if "paths" in data and len(data["paths"]) > 0:
            path = data["paths"][0]
            return {
                "distance_m": path.get("distance", 0),
                "time_ms": path.get("time", 0),
            }
    except Exception:
        pass

    return None


@task
def save_auto_edge(from_uuid: str, to_uuid: str, route: dict) -> bool:
    """Save auto edge to ArangoDB."""
    logger = get_run_logger()
    db = get_db()

    if not db.has_collection("edges"):
        db.create_collection("edges", edge=True)

    edges = db.collection("edges")

    edge_key = f"{from_uuid}_{to_uuid}_auto"
    doc = {
        "_key": edge_key,
        "_from": f"nodes/{from_uuid}",
        "_to": f"nodes/{to_uuid}",
        "type": "calculated",
        "transport_type": "auto",
        "distance_km": round(route["distance_m"] / 1000, 2),
        "travel_time_seconds": int(route["time_ms"] / 1000),
        "source": "graphhopper",
        "priority": 3,
        "calculated_at": datetime.utcnow().isoformat(),
    }

    if edges.has(edge_key):
        edges.update(doc)
        logger.info(f"Updated auto edge: {from_uuid} -> {to_uuid}")
    else:
        edges.insert(doc)
        logger.info(f"Created auto edge: {from_uuid} -> {to_uuid}")

    return True


@task
def find_rail_stations(node_uuid: str) -> list[dict]:
    """Find all nodes with 'rail' transport type."""
    logger = get_run_logger()
    db = get_db()

    aql = """
    FOR node IN nodes
      FILTER node._key != @node_uuid
      FILTER 'rail' IN node.transport_types
      RETURN {
        uuid: node._key,
        name: node.name,
        latitude: node.latitude,
        longitude: node.longitude
      }
    """

    cursor = db.aql.execute(aql, bind_vars={"node_uuid": node_uuid})
    stations = list(cursor)
    logger.info(f"Found {len(stations)} rail stations")
    return stations


@task(retries=2, retry_delay_seconds=10, timeout_seconds=120)
def calculate_rail_route(from_lat: float, from_lon: float, to_lat: float, to_lon: float) -> dict | None:
    """Calculate route via OpenRailRouting."""
    url = f"{RAIL_ROUTING_URL}/route"
    params = {
        "point": [f"{from_lat},{from_lon}", f"{to_lat},{to_lon}"],
        "profile": "all_tracks",
    }

    try:
        with httpx.Client(timeout=httpx.Timeout(30.0, read=120.0)) as client:
            response = client.get(url, params=params)
            if response.status_code != 200:
                return None
            data = response.json()

        if "paths" in data and len(data["paths"]) > 0:
            path = data["paths"][0]
            return {
                "distance_m": path.get("distance", 0),
                "time_ms": path.get("time", 0),
            }
    except Exception:
        pass

    return None


@task
def save_rail_edge(from_uuid: str, to_uuid: str, route: dict) -> bool:
    """Save rail edge to ArangoDB."""
    logger = get_run_logger()
    db = get_db()

    if not db.has_collection("edges"):
        db.create_collection("edges", edge=True)

    edges = db.collection("edges")

    edge_key = f"{from_uuid}_{to_uuid}_rail"
    doc = {
        "_key": edge_key,
        "_from": f"nodes/{from_uuid}",
        "_to": f"nodes/{to_uuid}",
        "type": "calculated",
        "transport_type": "rail",
        "distance_km": round(route["distance_m"] / 1000, 2),
        "travel_time_seconds": int(route["time_ms"] / 1000),
        "source": "openrailrouting",
        "priority": 2,
        "calculated_at": datetime.utcnow().isoformat(),
    }

    if edges.has(edge_key):
        edges.update(doc)
        logger.info(f"Updated rail edge: {from_uuid} -> {to_uuid}")
    else:
        edges.insert(doc)
        logger.info(f"Created rail edge: {from_uuid} -> {to_uuid}")

    return True


@flow(name="sync-node", log_prints=True)
def sync_node(
    node_data: dict | None = None,
    node_uuid: str | None = None,
    duration_minutes: int = 1200
):
    """
    Sync node to Optovia Maps graph.

    Args:
        node_data: Node data dict (for user addresses from Teams).
                   If provided, used directly without fetching from Odoo.
        node_uuid: Node UUID (for logistics nodes from Odoo).
                   If node_data is None, fetches from Odoo API.
        duration_minutes: Isochrone duration in minutes (default 20 hours).

    Concurrency is controlled by serve(limit=4) in deploy.py.
    """
    logger = get_run_logger()

    # 1. Get node data (either from parameter or fetch from Odoo)
    if node_data:
        logger.info(
            f"Starting sync for node: {node_data['uuid']} "
            f"(type={node_data.get('node_type', 'unknown')}, provided directly)"
        )
    elif node_uuid:
        logger.info(f"Starting sync for node: {node_uuid} (fetching from Odoo)")
        node_data = fetch_node(node_uuid)
    else:
        raise ValueError("Either node_data or node_uuid must be provided")

    # 2. Save node to ArangoDB
    save_node(node_data)

    transport_types = node_data.get("transport_types", [])
    node_uuid = node_data["uuid"]

    # 3. Process automotive transport
    if "auto" in transport_types:
        logger.info("Processing automotive transport...")

        # Calculate isochrone
        geometry = calculate_isochrone(
            node_data["latitude"],
            node_data["longitude"],
            node_uuid,
        )

        if geometry:
            save_isochrone(node_uuid, geometry, duration_minutes)

            # Find neighbors and calculate routes
            neighbors = find_auto_neighbors(node_uuid)

            for neighbor in neighbors:
                route = calculate_auto_route(
                    node_data["latitude"],
                    node_data["longitude"],
                    neighbor["latitude"],
                    neighbor["longitude"],
                )

                if route:
                    save_auto_edge(node_uuid, neighbor["uuid"], route)

    # 4. Process railway transport
    if "rail" in transport_types:
        logger.info("Processing railway transport...")

        stations = find_rail_stations(node_uuid)

        for station in stations:
            route = calculate_rail_route(
                node_data["latitude"],
                node_data["longitude"],
                station["latitude"],
                station["longitude"],
            )

            if route:
                save_rail_edge(node_uuid, station["uuid"], route)

    logger.info(f"Completed sync for node: {node_uuid}")


if __name__ == "__main__":
    # For testing
    import sys
    if len(sys.argv) > 1:
        sync_node(sys.argv[1])
