"""
Implements creation of geojsons for visualizations.
"""
import os
import json
import threading
import logging
import time
from typing import Dict, List, Union

from pyproj import Transformer
from flask import Blueprint, send_file, Response
from flask import current_app as app

from app.api import kpis as kpis_module
from app import utils


geojsons = Blueprint("geojsons", __name__)

logger = logging.getLogger(__name__)

ts_url = app.dotenv["TS_URL"]
storage_url = app.dotenv["STORAGE_URL"]
city = app.dotenv["URBANITE_CITY"]
data_dir = f"{app.dotenv['DATA_DIR']}/{app.dotenv['URBANITE_CITY']}"


# DEBUG
@app.route("/dss/geojson/test/<sim_id>")
def geojsontest(sim_id):
    app.logger.debug("starting test")
    kpi_name = "pedestrianTravelTime"
    app.logger.debug("starting load network and events")
    network, events = utils.get_network_and_events(sim_id)
    app.logger.debug("done")
    app.logger.debug("starting calculate pedestrian travel time")
    start_t = time.time()
    pedestrianTravelTime = events.pedestrian_travel_time()["detailed_long_trips"]
    delta_t = time.time() - start_t
    app.logger.debug("done in %s s", delta_t)
    app.logger.debug("starting write geojson pedestrian trips")
    start_t = time.time()
    pedestrianTravelTime = write_geojson_pedestrian_trips(pedestrianTravelTime, network, sim_id)
    delta_t = time.time() - start_t
    app.logger.info("written pedestrianTravelTime in %s s", delta_t)
    return pedestrianTravelTime



@app.route("/dss/geojson/<int:sim_id>/<kpi_name>")
def get_kpi_geojson(sim_id, kpi_name):
    sim_dir = utils.get_sim_dir_names([sim_id])[0]
    path = f"{data_dir}/simulations/{sim_id}/results/{sim_dir}/kpi_vis_{kpi_name}.json"
    app.logger.warning("path to get kpi vis: %s", path)
    # check if file exists
    if os.path.exists(path) and os.stat(path).st_size != 0:
        app.logger.info("returning file at %s", path)
        return send_file(path)
    else:
        # does not exist, create it
        app.logger.info("Starting to calculate geojson.")
        calculate_geojson(sim_id)
    return {"message": "Calculating..."}


@app.route("/dss/map-info", methods=["GET"])
def get_map_info():
    lat, lon = app.dotenv["CITY_COORDINATES"].split(",")
    zoom_level = app.dotenv["CITY_ZOOM_LEVEL"]
    return {"coordinates": {"lat": lat, "lon": lon}, "zoom": zoom_level}


@app.route("/dss/kpi-vis-info", methods=["GET"])
def get_kpi_vis_info():
    kpi_info = []
    if city == "bilbao":
        kpi_info = [
            {"buttonValue": "pollution", "buttonLabel": "Pollution"},
            {
                "buttonValue": "pedestrianTravelTime",
                "buttonLabel": "Pedestrian Travel Time",
            },
            {"buttonValue": "dailyInternalTravels", "buttonLabel": "Internal Trips"},
        ]
    elif city == "amsterdam":
        kpi_info = [
            {"buttonValue": "pollution", "buttonLabel": "Pollution"},
            {"buttonValue": "bikeability", "buttonLabel": "Bikeability"},
            {"buttonValue": "bikeSafety", "buttonLabel": "Bike Traffic Safety"},
            {"buttonValue": "bikeIntensity", "buttonLabel": "Bike Intensity"},
            {"buttonValue": "bikeCongestion", "buttonLabel": "Bike Congestions"},
        ]
    elif city == "helsinki":
        kpi_info = [
            {"buttonValue": "pollution", "buttonLabel": "Pollution"},
            {
                "buttonValue": "accousticPollution",
                "buttonLabel": "Acoustic Pollution - WIP",
            },
            {
                "buttonValue": "congestionsAndBottlenecks",
                "buttonLabel": "Congestions and Bottlenecks",
            },
            {
                "buttonValue": "harbourAreaTrafficFlow",
                "buttonLabel": "Harbour Area Traffic Flow",
            },
        ]
    elif city == "messina":
        kpi_info = [
            {"buttonValue": "pollution", "buttonLabel": "Pollution"},
            {
                "buttonValue": "publicTransportUse",
                "buttonLabel": "Public Transport Use",
            },
            {
                "buttonValue": "averageSpeedOfPublicTransport",
                "buttonLabel": "Avg Public Transport Speed",
            },
            # {"buttonValue": "numberOfBikeTrips", "buttonLabel": "Number of Bike Trips"},
            {
                "buttonValue": "shareOfVehicles",
                "buttonLabel": "Share of Trips per Mode",
            },
            {
                "buttonValue": "dailyInternalTravels",
                "buttonLabel": "Daily Internal Trips",
            }
        ]
    else:
        raise ValueError("Invalid deploy parameters - city set to %s", city)
    return Response(json.dumps(kpi_info), mimetype="application/json")


def get_kpi_data_and_write(network, events, sim_id):
    """
    get_kpi_data_final_geojson is a list of links with kpis calculated
    """
    app.logger.info(f"getting kpi data to write, city {city}")

    results = {}
    emisson_object = kpis_module.get_emissions(sim_id, network)
    rush_hour = utils.get_max_traffic_time(sim_id)

    if network.city == "bilbao":
        if emisson_object is not None:
            pollution = emisson_object.total_emissions_by_link()
            app.logger.info("Emission by link")
            write_geojson_links(pollution, "pollution", network, sim_id)
            app.logger.info("written emission")

        pedestrianTravelTime = events.pedestrian_travel_time()["detailed_long_trips"]
        app.logger.info("Pedestrian travel time")
        write_geojson_pedestrian_trips(pedestrianTravelTime, network, sim_id)
        app.logger.info("written pedestrianTravelTime")

        dailyInternalTravels = events.vehicles_count_per_link()
        app.logger.info("Vehicles count per link")
        write_geojson_links(dailyInternalTravels, "dailyInternalTravels", network, sim_id)
        app.logger.info("written dailyInternalTravels")

    elif network.city == "helsinki":
        if emisson_object is not None:
            pollution = emisson_object.total_emissions_by_link()
            app.logger.info("total emissions by link")
            write_geojson_links(pollution, "pollution", network, sim_id)
            app.logger.info("written emission")

        # acousticPollution = None  # TODO integrate with Inaki
        # app.logger.info("accoustic pollution - TODO")
        # write_geojson(pollution, "pollution", network, sim_id)
        # app.logger.info("written emission")

        congestionsAndBottlenecks = events.get_congested_links(rush_hour)["congested_links"]
        app.logger.info("congestions and bottlenecks")
        write_geojson_links(
            congestionsAndBottlenecks, "congestionsAndBottlenecks", network, sim_id
        )
        app.logger.info("written congestionsAndBottlenecks")

        results["harbourAreaTrafficFlow"] = None  # TODO implement!
        app.logger.info("harbour area traffic flow - TODO")

    elif network.city == "messina":
        
        if emisson_object is not None:
            pollution = emisson_object.total_emissions_by_link()
            app.logger.info("Emission by link")
            write_geojson_links(pollution, "pollution", network, sim_id)
            app.logger.info("written emission")


        publicTransportUse = events.public_transport_use_geojson()
        app.logger.info("public transport use")
        write_geojson_links(publicTransportUse, "publicTransportUse", network, sim_id)
        app.logger.info("written public transport use")


        averageSpeedOfPublicTransport = events.average_bus_speed_geojson()
        app.logger.info("average speed of public transport")
        write_geojson_links(
            averageSpeedOfPublicTransport,
            "averageSpeedOfPublicTransport",
            network,
            sim_id,
        )
        app.logger.info("written averageSpeedOfPublicTransport")
        

        numberOfBikeTrips = events.vehicles_count_per_link(only="bike")
        app.logger.info("number of bike trips")
        write_geojson_links(numberOfBikeTrips, "numberOfBikeTrips", network, sim_id)
        app.logger.info("written numberOfBikeTrips")

        shareOfVehicles = vehicleCountToShareGeojson(events.vehicles_count_per_link())
        app.logger.info("share of vehicles")
        write_geojson_links(shareOfVehicles, "shareOfVehicles", network, sim_id)
        app.logger.info("written shareOfVehicles")

        dailyInternalTravels = events.vehicles_count_per_link()
        app.logger.info("daily internal travels")
        write_geojson_links(dailyInternalTravels, "dailyInternalTravels", network, sim_id)
        app.logger.info("written dailyInternalTravels")

    elif network.city == "amsterdam":
        if emisson_object is not None:
            pollution = emisson_object.total_emissions_by_link()
            app.logger.info("got emissions by link")
            write_geojson_links(pollution, "pollution", network, sim_id)
            app.logger.info("written pollution")

        bikeability = events.bikeability_index()
        app.logger.info("bikeability")
        write_geojson_links(bikeability, "bikeability", network, sim_id)
        app.logger.info("written bikeability")

        bike_safety = events.bike_safety_index()
        app.logger.info("bike safety")
        write_geojson_links(bike_safety, "bikeSafety", network, sim_id)
        app.logger.info("written bikeSafety")

        bike_intensity = events.vehicles_count_per_link()
        app.logger.info("bikeIntensity")
        write_geojson_links(bike_intensity, "bikeIntensity", network, sim_id)
        app.logger.info("written bikeIntensity")

        bike_congestion = events.get_congested_links(rush_hour, vehicle_mode="bicycle")["congested_links"]
        app.logger.info("bikeCongestion")
        write_geojson_links(bike_congestion, "bikeCongestion", network, sim_id)
        app.logger.info("written bikeCongestion")

        app.logger.info("done")


def vehicleCountToShareGeojson(data_per_link):
    results = {}
    sum_vehicles_per_link = {}
    for link, data in data_per_link.items():
        n_veh = 0
        for key in data:
            n_veh += data[key]
        sum_vehicles_per_link[link] = sum_vehicles_per_link.get(link, 0) + n_veh
    for link, data in data_per_link.items():
        if link.startswith("pt"):
            continue
        n_veh = sum_vehicles_per_link[link]
        if n_veh < 1:
            continue
        results[link] = {
            "bike": data["bike_count"] / n_veh,
            "car": data["car_count"] / n_veh,
            "bus": data["bus_count"] / n_veh,
        }
    return results


def write_geojson_pedestrian_trips(kpi_data, network, sim_id):
    """
    Write a pedestrian kpi_data object to a file.
    kpi_data should be a list [{"start_link": _, "end_link": _, "average_time": _ } ]
    """
    data = {"type": "FeatureCollection", "features": []}
    crs_transformer = Transformer.from_crs(network.crs_epsg, "epsg:4326")
    # crs_transformer = Transformer.from_crs("epsg:2062", "epsg:4326")

    for element in kpi_data:
        if element["average_time"] < 3600.: continue

        s_link = network.get_link(element["start_link"])
        s_nodes = [network.get_node(s_link["from"]), network.get_node(s_link["to"]) ]
        s_point = (
            (float(s_nodes[0]["x"]) + float(s_nodes[1]["x"])) / 2.,
            (float(s_nodes[0]["y"]) + float(s_nodes[1]["y"])) / 2.
        )
        s_point = tuple(reversed(crs_transformer.transform(*s_point)))
        e_link = network.get_link(element["end_link"])
        e_nodes = [network.get_node(e_link["from"]), network.get_node(e_link["to"])]
        e_point = (
            (float(e_nodes[0]["x"]) + float(e_nodes[1]["x"])) / 2.,
            (float(e_nodes[0]["y"]) + float(e_nodes[1]["y"])) / 2.
        )
        e_point = tuple(reversed(crs_transformer.transform(*e_point)))
        data["features"].append(
            {
                "type": "Feature",
                "properties": {
                    "long pedestrian trip": element["average_time"] / (60)
                },
                "geometry": {
                    "type": "LineString",
                    "coordinates": [s_point, e_point]
                }
            }
        )

    filename = get_filename(sim_id, "pedestrianTravelTime")
    with open(filename, "w") as fp:
        json.dump(data, fp, indent=2)
    return data


def write_geojson_links(kpi_data, kpi_name, network, sim_id):
    """
    Write a per link kpi_data object to a file.
    kpi_data should be a dict { "link_id": {kpi1, kpi2, ...}, ...}
    """
    # logger.debug("KPI DATA to be written: \n%s", kpi_data)
    data = {"type": "FeatureCollection", "features": []}
    crs_transformer = Transformer.from_crs(network.crs_epsg, "epsg:4326")
    logger.warning("network city: %s, network epsg: %s", network.city, network.crs_epsg)
    for link_id, link_data in kpi_data.items():
        if link_id.startswith("pt"):
            continue
        # if isinstance(link_data, float):
        #     link_data = {kpi_name: link_data}
        elif len(link_data) < 1:
            continue
        link = network.get_link(link_id)

        from_coords_orig = (
            float(network.get_node(link["from"])["x"]),
            float(network.get_node(link["from"])["y"]),
        )
        from_coords = tuple(reversed(crs_transformer.transform(*from_coords_orig)))
        to_coords_orig = (
            float(network.get_node(link["to"])["x"]),
            float(network.get_node(link["to"])["y"]),
        )
        to_coords = tuple(reversed(crs_transformer.transform(*to_coords_orig)))
        data["features"].append(
            {
                "type": "Feature",
                "properties": {},
                "geometry": {
                    "type": "LineString",
                    "coordinates": [from_coords, to_coords],
                },
            }
        )
        data["features"][-1]["properties"] = link_data

    filename = get_filename(sim_id, kpi_name)
    with open(filename, "w") as fp:
        json.dump(data, fp, indent=2)


def get_filename(sim_id, kpi_name):
    """
    Returns the filename for the simulation and kpi provided.
    """
    date = utils.get_sim_dir_names([sim_id])[0]
    return f"{data_dir}/simulations/{sim_id}/results/{date}/kpi_vis_{kpi_name}.json"


def get_kpi_names(
    kpis_obj: Union[Dict, float], kpi_names=[], last_kpi=None
) -> List[str]:
    """
    Returns a list of kpi names from the kpis_obj.
    """
    for key, value in kpis_obj.items():
        if isinstance(value, Dict):
            kpi_names += get_kpi_names(value, kpi_names, key)
        else:
            kpi_names.append(key)
    return kpi_names


def write_final_geojson(simulation_id, app_con=None):
    """
    Calculates the kpi_vis geojson for visualization of KPIs.
    :param sim_id: simulation id
    """
    if app_con is not None:
        app = app_con.app
    else:
        from flask import current_app as app
    app.logger.info("write final geojson call")

    # Check if thread is running
    thread_running = False
    for thread in threading.enumerate():
        if "get_kpi_data_and_write" in thread.name:
            thread_running = True
            logger.info("calculate kpi_vis thread already running")

    if not thread_running:
        with app.app_context():
            app.logger.info("network, events loading")
            network, events = utils.get_network_and_events(simulation_id)
            app.logger.info("network, events loaded")
        with app.app_context():
            get_kpi_data_and_write_thread = threading.Thread(
                target=get_kpi_data_and_write(network, events, simulation_id)
            )
    if thread_running:
        return {"message": "still preparing..."}
    else:
        return {"message": "preparing visualizations."}


def calculate_geojson(sim_id):
    """
    Calculates the geojson for visualization of KPI values on the streets.
    :param sim_id: simulation id
    :returns: status
    """
    app.logger.info("starting a calculate kpi_vis thread...")
    # Check if thread is running
    thread_running = False
    for thread in threading.enumerate():
        if "write_final_geojson" in thread.name:
            thread_running = True
            logger.info("calculate kpi_vis thread already running")
            break
    # If thread is not running than start it
    if not thread_running:
        app_con = app.app_context()
        create_kpi_vis_thread = threading.Thread(
            target=write_final_geojson, args=[sim_id, app_con]
        )
        create_kpi_vis_thread.start()
        logger.info("started a calculate kpi_vis thread.")
    else:
        return {"message": "Still preparing the KPI visualization."}
    return {"message": "Preparing the KPI visualization."}