import os
import json
import gzip
import random
from datetime import datetime, timedelta
import colorsys
from math import radians, cos, sin, asin, sqrt

from django.contrib.gis.geos import Point, LineString
import requests

from ..models import Marker, Trip
from website.settings import VIRTUALEARTH_API_KEY

max_time_diff = timedelta(hours=6)
max_distance = 3000 # m

class TripConverter:
  trips: list[Trip]
  markers: list[Marker]
  created: int
  updated: int
  skipped: int

  def __init__(self, startDate: datetime=None):
    if startDate:
      self.markers = Marker.objects.filter(timestamp__gte=startDate).all()
      self.trips = list(Trip.objects.filter(endTime__gte=startDate).only("startTime", "endTime", "totalTime"))
    else:
      self.markers = Marker.objects.all()
      self.trips = list(Trip.objects.only("startTime", "endTime", "totalTime"))
    self.created = 0
    self.updated = 0
    self.skipped = 0

  def run(self):
    tmp: list[Marker] = []
    for prev, marker in zip(self.markers, self.markers[1:]):
      if marker.timestamp - prev.timestamp > max_time_diff or Distance(marker.location, prev.location) > max_distance:
        if len(tmp) > 1:
          self.trips.append(self.create_trip(tmp))
        tmp = []
      tmp.append(marker)
    if len(tmp) > 1:
      self.trips.append(self.create_trip(tmp))

  def create_trip(self, markers: list[Marker]) -> Trip:
    print(len(markers), markers[0].timestamp, markers[-1].timestamp)
    trip = None
    for current in self.trips:
      if current.startTime <= markers[-1].timestamp and current.endTime >= markers[0].timestamp:
        trip = current
        self.trips.remove(current)
        break
    
    center = markers[len(markers)//2].location
    if not trip:
      trip = Trip(
        startTime = markers[0].timestamp,
        endTime = markers[-1].timestamp,
        name = f"Trip {markers[0].timestamp}",
        color = get_path_color(markers[0].timestamp),
        center = Point(center.x, center.y)
      )
      self.created += 1
      self.updated -= 1
    elif trip.startTime == markers[0].timestamp and \
        trip.endTime == markers[-1].timestamp and \
        trip.totalTime == markers[-1].timestamp - markers[0].timestamp:
      print("Trip already exists")
      self.skipped += 1
      return trip

    if not trip.name or "None" in trip.name or trip.name.startswith("Trip "):
      start = get_location_name(markers[0].location)
      end = get_location_name(markers[-1].location)
      if start != end:
        trip.name = f"{start} - {end}"
      else:
        trip.name = start
    trip.startTime = markers[0].timestamp
    trip.endTime = markers[-1].timestamp
    trip.totalTime = trip.endTime - trip.startTime
    trip.center = Point(center.x, center.y)

    total_distance = 0 # m
    topSpeed = 0 # km/h
    ascendHeight = 0 # m
    descendHeight = 0 # m
    movementTime = timedelta(0)

    minLat = minLon = maxLat = maxLon = None

    lastSpeed = 0
    i = 1

    while i < len(markers):
      point = markers[i]
      prev_point = markers[i-1]
      dist = Distance(point.location, prev_point.location)
      if point.speed is not None and point.speed > 0:
        speed = point.speed
      else:
        speed = dist / abs(point.timestamp - prev_point.timestamp).seconds * 3.6

      if abs(speed - lastSpeed) / abs(point.timestamp - prev_point.timestamp).seconds > 10: # m/s²
        markers.remove(point)
        continue

      if abs(speed - lastSpeed) > 50: # m/s
        markers.remove(point)
        continue

      total_distance += dist
      topSpeed = max(topSpeed, speed)

      minLat = min(minLat, point.location.y) if minLat is not None else point.location.y
      minLon = min(minLon, point.location.x) if minLon is not None else point.location.x
      maxLat = max(maxLat, point.location.y) if maxLat is not None else point.location.y
      maxLon = max(maxLon, point.location.x) if maxLon is not None else point.location.x
      
      if speed > 2.0: # km/h
        movementTime += abs(point.timestamp - prev_point.timestamp)

      if point.alt is not None and prev_point.alt is not None:
        if point.alt > prev_point.alt:
          ascendHeight += point.alt - prev_point.alt
        else:
          descendHeight += prev_point.alt - point.alt
      i += 1

    trip.distance = round(total_distance, 1) # m
    trip.topSpeed = round(topSpeed, 1) # km/h
    trip.avgSpeed = round(total_distance / (movementTime or trip.endTime - trip.startTime).total_seconds() * 3.6, 1) # km/h
    trip.ascendHeight = round(ascendHeight, 1) # m
    trip.descendHeight = round(descendHeight, 1) # m
    trip.movementTime = movementTime

    trip.line = LineString((minLon, minLat), (maxLon, maxLat))

    trip.path = points_to_blob(markers)
    trip.save()
    self.updated += 1

    return trip

def Distance(point1: Point, point2: Point) -> float:
  lon1 = radians(point1.x)
  lon2 = radians(point2.x)
  lat1 = radians(point1.y)
  lat2 = radians(point2.y)
  
  # Haversine formula 
  dlon = lon2 - lon1 
  dlat = lat2 - lat1 
  a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
  c = 2 * asin(sqrt(a)) 
  r = 6371000 # Radius of earth in meters. Use 3956 for miles
  return c * r

def get_location_name(point: Point) -> str:
  url = f"https://dev.virtualearth.net/REST/v1/LocationRecog/{round(point.y, 6)},{round(point.x, 6)}?radius=2&top=1&c=de-DE&includeEntityTypes=address&key={VIRTUALEARTH_API_KEY}&output=json"
  response = requests.get(url)
  if response.status_code == 200:
    data = response.json()
    if data["resourceSets"][0]["estimatedTotal"] > 0:
      address = data["resourceSets"][0]["resources"][0]["addressOfLocation"][0]
      if address["locality"] and address["neighborhood"]:
        txt = f"{address['locality']} {address['neighborhood']}"
      else:
        txt = address["adminDivision"]
      
      if address["countryIso2"]:
        txt += f" {address['countryIso2']}"
      return txt
  return None

def get_path_color(time: datetime) -> str:
  random.seed(int(time.timestamp()))
  hue = random.random()
  saturation = 0.5 + random.random() / 2
  value = 0.5 + random.random() / 2
  rgb = colorsys.hsv_to_rgb(hue, saturation, value)
  return f"#{int(rgb[0]*255):02x}{int(rgb[1]*255):02x}{int(rgb[2]*255):02x}"

def points_to_blob(markers) -> bytes:
  arr = []

  for marker in markers:
    arr.append({
      "lat": marker.location.y,
      "lng": marker.location.x,
      "alt": marker.alt,
      "hdop": marker.hdop,
      "speed": marker.speed,
      "timestamp": marker.timestamp.timestamp(),
    })
  data = json.dumps(arr).encode('utf-8')
  return gzip.compress(data)