from prefect import flow, task
import requests
import json
import logging
import uuid
import math
from typing import Dict, Any, List
from arango import ArangoClient

logger = logging.getLogger(__name__)


def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
    """Вычисляет геодезическое расстояние между двумя точками (формула Haversine)"""
    
    # Радиус Земли в км
    R = 6371.0
    
    # Конвертируем градусы в радианы
    lat1_rad = math.radians(lat1)
    lon1_rad = math.radians(lon1)
    lat2_rad = math.radians(lat2)
    lon2_rad = math.radians(lon2)
    
    # Разности координат
    dlat = lat2_rad - lat1_rad
    dlon = lon2_rad - lon1_rad
    
    # Формула Haversine
    a = math.sin(dlat / 2)**2 + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlon / 2)**2
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
    distance = R * c
    
    return round(distance, 2)


@task
def create_isochrone_valhalla(latitude: float, longitude: float, duration_minutes: int) -> Dict[str, Any]:
    """Создает изохрон через Valhalla API для 20 часов езды на авто"""
    
    valhalla_url = "http://valhalla_local:8002/isochrone"
    
    payload = {
        "locations": [{"lat": latitude, "lon": longitude}],
        "costing": "auto",
        "contours": [{"time": duration_minutes}],
        "polygons": True,
        "denoise": 0.3,
        "generalize": 100
    }
    
    try:
        response = requests.post(valhalla_url, json=payload, timeout=300)
        response.raise_for_status()
        
        result = response.json()
        
        if 'features' in result and len(result['features']) > 0:
            logger.info(f"Изохрон успешно создан для координат {latitude}, {longitude}")
            return result
        else:
            raise ValueError("Valhalla вернул пустой результат")
            
    except requests.exceptions.Timeout:
        logger.error(f"Timeout при создании изохрона для {latitude}, {longitude}")
        raise
    except requests.exceptions.RequestException as e:
        logger.error(f"Ошибка Valhalla API: {str(e)}")
        raise
    except Exception as e:
        logger.error(f"Неожиданная ошибка: {str(e)}")
        raise


@task
def add_node_to_arango(node_uuid: str, latitude: float, longitude: float, country: str, connection_types: list) -> str:
    """Добавляет ноду в граф ArangoDB"""
    
    # Подключение к ArangoDB
    client = ArangoClient(hosts='http://arangodb_local:8529')
    db = client.db('_system', username='root', password='arangodb_password')
    
    # Создаем коллекцию нод если не существует
    if not db.has_collection('nodes'):
        collection = db.create_collection('nodes')
        logger.info("Коллекция nodes создана")
    else:
        collection = db.collection('nodes')
    
    # Формируем документ ноды
    node_document = {
        "_key": node_uuid,
        "latitude": latitude,
        "longitude": longitude,
        "country": country,
        "connection_types": connection_types
    }
    
    # Сохраняем в ArangoDB
    result = collection.insert(node_document, overwrite=True)
    
    logger.info(f"Нода добавлена в граф ArangoDB с UUID: {node_uuid}, страна: {country}")
    return node_uuid


@task
def save_isochrone_to_arangodb(node_uuid: str, duration_minutes: int, 
                              isochrone_geojson: Dict[str, Any]) -> str:
    """Сохраняет изохрон в ArangoDB и возвращает UUID"""
    
    # Генерируем UUID для изохрона
    isochrone_uuid = str(uuid.uuid4())
    
    # Подключение к ArangoDB
    client = ArangoClient(hosts='http://arangodb_local:8529')
    db = client.db('_system', username='root', password='arangodb_password')
    
    # Создаем коллекцию изохронов если не существует
    if not db.has_collection('isochrones'):
        collection = db.create_collection('isochrones')
        # Создаем гео-индекс
        collection.add_geo_index(['geometry'])
        logger.info("Коллекция isochrones создана с гео-индексом")
    else:
        collection = db.collection('isochrones')
    
    # Формируем документ для ArangoDB
    document = {
        "_key": isochrone_uuid,
        "node_uuid": node_uuid,
        "duration_minutes": duration_minutes,
        "geometry": isochrone_geojson,
        "created_at": "2025-09-04T03:00:00Z"
    }
    
    # Сохраняем в ArangoDB
    result = collection.insert(document, overwrite=True)
    
    logger.info(f"Изохрон сохранен в ArangoDB с UUID: {isochrone_uuid} для ноды {node_uuid}")
    return isochrone_uuid


@task  
def create_isochrone_record(node_uuid: str, duration_minutes: int, 
                           isochrone_uuid: str, odoo_url: str) -> bool:
    """Создает запись изохрона в Odoo с UUID из ArangoDB"""
    
    # Формируем URL для FastAPI endpoint в Odoo
    create_url = f"{odoo_url}/fastapi/logistics/isochrones/create"
    
    payload = {
        "node_uuid": node_uuid,
        "duration_minutes": duration_minutes,
        "uuid": isochrone_uuid
    }
    
    response = requests.post(create_url, json=payload, timeout=30)
    response.raise_for_status()
    logger.info(f"Запись изохрона создана для ноды {node_uuid} с UUID изохрона: {isochrone_uuid}")
    return True


@task  
def find_compatible_nodes(node_uuid: str, country: str, connection_types: list) -> List[Dict]:
    """Находит совместимые ноды в той же стране с пересекающимися типами соединений"""
    
    # Подключение к ArangoDB
    client = ArangoClient(hosts='http://arangodb_local:8529')
    db = client.db('_system', username='root', password='arangodb_password')
    
    if not db.has_collection('nodes'):
        logger.info("Коллекция nodes не существует")
        return []
    
    collection = db.collection('nodes')
    
    # AQL запрос для поиска нод той же страны с совместимыми типами соединений
    aql = """
    FOR node IN nodes
        FILTER node.country == @country
        FILTER node._key != @node_uuid
        FILTER LENGTH(INTERSECTION(node.connection_types, @connection_types)) > 0
        RETURN {
            uuid: node._key,
            name: node.name,
            latitude: node.latitude,
            longitude: node.longitude,
            connection_types: node.connection_types
        }
    """
    
    cursor = db.aql.execute(aql, bind_vars={
        'country': country,
        'node_uuid': node_uuid,
        'connection_types': connection_types
    })
    
    compatible_nodes = [doc for doc in cursor]
    logger.info(f"Найдено {len(compatible_nodes)} совместимых нод в стране {country}")
    return compatible_nodes


@task
def create_graph_edges(node_uuid: str, node_lat: float, node_lon: float, 
                      target_nodes: List[Dict], calculate_method: str) -> int:
    """Создает ребра графа с указанным методом расчета"""
    
    # Подключение к ArangoDB
    client = ArangoClient(hosts='http://arangodb_local:8529')
    db = client.db('_system', username='root', password='arangodb_password')
    
    # Создаем коллекцию ребер если не существует
    if not db.has_collection('connections'):
        collection = db.create_collection('connections', edge=True)
        logger.info("Коллекция connections создана")
    else:
        collection = db.collection('connections')
    
    edges_created = 0
    
    for target_node in target_nodes:
        # Вычисляем расстояние
        distance = calculate_distance(
            node_lat, node_lon,
            target_node['latitude'], target_node['longitude']
        )
        
        # Создаем ребро
        edge_document = {
            "_from": f"nodes/{node_uuid}",
            "_to": f"nodes/{target_node['uuid']}",
            "type": "auto",
            "distance": distance,
            "calculate_method": calculate_method
        }
        
        collection.insert(edge_document, overwrite=True)
        edges_created += 1
    
    logger.info(f"Создано {edges_created} ребер для ноды {node_uuid} методом {calculate_method}")
    return edges_created


@flow(name="add-node-to-graph")
def logistics_node_to_graph_flow(node_uuid: str, country: str, latitude: float, longitude: float, 
                                connection_types: list, odoo_url: str, graph_method: str = "geodesic", 
                                duration_minutes: int = 1200):
    """Универсальный flow для добавления ноды в граф"""
    
    logger.info(f"Добавляем ноду {node_uuid} в граф методом {graph_method}, страна {country}")
    
    # Шаг 1: Добавляем ноду в граф ArangoDB
    add_node_to_arango(
        node_uuid,
        latitude,
        longitude,
        country,
        connection_types
    )
    
    target_nodes = []
    
    if graph_method == "isochrone":
        # Шаг 3a: Создаем изохрон через Valhalla
        isochrone_result = create_isochrone_valhalla(latitude, longitude, duration_minutes)
        
        # Шаг 4a: Сохраняем изохрон в ArangoDB
        isochrone_uuid = save_isochrone_to_arangodb(node_uuid, duration_minutes, isochrone_result)
        
        # Шаг 5a: Создаем запись изохрона в Odoo
        create_isochrone_record(node_uuid, duration_minutes, isochrone_uuid, odoo_url)
        
        # TODO: Найти ноды внутри изохрона
        logger.info("TODO: Найти ноды внутри изохрона")
        
    elif graph_method == "geodesic":
        # Шаг 2: Находим совместимые ноды в той же стране
        target_nodes = find_compatible_nodes(node_uuid, country, connection_types)
    
    # Шаг 6: Создаем ребра графа
    if target_nodes:
        edges_count = create_graph_edges(node_uuid, latitude, longitude, target_nodes, graph_method)
    else:
        edges_count = 0
        
    logger.info(f"Граф создан для ноды {node_uuid}: {edges_count} соединений методом {graph_method}")
    return {"status": "success", "node_uuid": node_uuid, "method": graph_method, "edges_created": edges_count}


if __name__ == "__main__":
    # Регистрируем flow в Prefect
    logistics_node_to_graph_flow.serve(
        name="graph-deployment",
        tags=["logistics", "graph", "nodes"]
    )