from dataclasses import dataclass
from math import asin, cos, radians, sin, sqrt
from typing import Optional

from django.apps import apps
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from locations.models import GeoLocation
from scrobbles.dataclasses import BaseLogData, WithPeopleLogData
from scrobbles.mixins import ScrobblableConstants, ScrobblableMixin

BNULL = {"blank": True, "null": True}


def haversine(lat1, lon1, lat2, lon2):
    R = 6371000
    dlat = radians(lat2 - lat1)
    dlon = radians(lon2 - lon1)
    a = sin(dlat / 2) ** 2 + cos(radians(lat1)) * cos(radians(lat2)) * sin(dlon / 2) ** 2
    return R * 2 * asin(sqrt(a))


@dataclass
class TrailLogData(BaseLogData, WithPeopleLogData):
    effort: Optional[str] = None
    difficulty: Optional[str] = None
    distance_km: Optional[float] = None
    elevation_gain_m: Optional[float] = None
    moving_time_seconds: Optional[int] = None
    activity_type: Optional[str] = None
    avg_speed_kmh: Optional[float] = None
    avg_heartrate: Optional[int] = None
    max_heartrate: Optional[int] = None
    calories: Optional[int] = None


class Trail(ScrobblableMixin):
    class PrincipalType(models.TextChoices):
        WOODS = "WOODS"
        ROAD = "ROAD"
        BEACH = "BEACH"
        MOUNTAIN = "MOUNTAIN"

    class ActivityType(models.TextChoices):
        WALK = "WALK"
        HIKE = "HIKE"
        RUN = "RUN"
        BIKE = "BIKE"

    description = models.TextField(**BNULL)
    trailhead_location = models.ForeignKey(
        GeoLocation,
        related_name="trailheads",
        on_delete=models.DO_NOTHING,
        **BNULL,
    )
    trail_terminus_location = models.ForeignKey(
        GeoLocation,
        related_name="trail_termini",
        on_delete=models.DO_NOTHING,
        **BNULL,
    )
    route_lat = models.FloatField(**BNULL)
    route_lon = models.FloatField(**BNULL)
    strava_id = models.CharField(max_length=255, **BNULL)
    trailforks_id = models.CharField(max_length=255, **BNULL)
    alltrails_id = models.CharField(max_length=255, **BNULL)
    gaiagps_id = models.CharField(max_length=255, **BNULL)
    principal_type = models.CharField(
        max_length=10, choices=PrincipalType.choices, **BNULL
    )
    default_activity_type = models.CharField(
        max_length=10, choices=ActivityType.choices, **BNULL
    )

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("trails:trail_detail", kwargs={"slug": self.uuid})

    @property
    def logdata_cls(self):
        return TrailLogData

    @property
    def strings(self) -> ScrobblableConstants:
        return ScrobblableConstants(verb="Moving", tags="runner")

    @classmethod
    def find_or_create(cls, title: str) -> "Trail":
        trail = cls.objects.filter(title__iexact=title).first()
        if not trail:
            trail = cls.objects.create(title=title)
        return trail

    @classmethod
    def find_by_trailhead(cls, lat, lon, tolerance_m=100, route_lat=None, route_lon=None):
        candidates = cls.objects.filter(
            trailhead_location__isnull=False,
        ).select_related("trailhead_location")
        best = None
        best_dist = float("inf")
        for trail in candidates:
            loc = trail.trailhead_location
            d = haversine(lat, lon, loc.lat, loc.lon)
            if d >= best_dist or d > tolerance_m:
                continue
            if route_lat is not None and trail.route_lat is not None:
                route_dist = haversine(route_lat, route_lon, trail.route_lat, trail.route_lon)
                if route_dist > tolerance_m:
                    continue
            best = trail
            best_dist = d
        return best

    def scrobbles(self, user_id):
        Scrobble = apps.get_model("scrobbles", "Scrobble")
        return Scrobble.objects.filter(user_id=user_id, life_event=self).order_by(
            "-timestamp"
        )
