use std::fs;
use std::fs::File;
use std::io::BufReader;

use geoutils::Location;
use gpx::read;
use gpx::Waypoint;
use serde::Serialize;

use super::{format_meters, french_months};

#[derive(Serialize, Debug, PartialEq, Clone, Copy)]
enum TracesType {
    Bike,
    Hike,
}

#[derive(Serialize, Debug)]
pub struct Trip {
    r#type: TracesType,
    type_as_string: &'static str,
    color: &'static str,
    date: String,
    name: String,
    distance: f64,
    distance_format: String,
    elevation_positive: f64,
    elevation_positive_format: String,
    elevation_negative: f64,
    elevation_negative_format: String,
    traces: Vec<Trace>,
}

#[derive(Serialize, Debug)]
struct Trace {
    path: String,
    name: String,
    distance: f64,
    distance_format: String,
    elevation_positive: f64,
    elevation_positive_format: String,
    elevation_negative: f64,
    elevation_negative_format: String,
}

pub fn load_trips() -> Vec<Trip> {
    let mut trips = vec![];

    for trip_dir in fs::read_dir("content/traces").unwrap() {
        let trip_dir = trip_dir.unwrap();

        let trip_path = trip_dir.path();
        let trip_filename = trip_path.file_name().unwrap().to_string_lossy();
        let (date, name) = trip_filename.split_once(' ').unwrap();

        let r#type = if name.ends_with("[bike]") {
            TracesType::Bike
        } else {
            TracesType::Hike
        };

        let (name, _) = name.split_once('[').unwrap();
        let (year, month) = date.split_once('-').unwrap();

        let mut trip = Trip {
            r#type,
            type_as_string: if r#type == TracesType::Bike {
                "Vélo"
            } else {
                "Randonnée"
            },
            date: format!("{} {year}", french_months(month)),
            color: if r#type == TracesType::Bike {
                "#2563eb"
            } else {
                "#ea580c"
            },
            name: name.trim().to_string(),
            distance: 0.0,
            distance_format: String::new(),
            elevation_negative: 0.0,
            elevation_negative_format: String::new(),
            elevation_positive: 0.0,
            elevation_positive_format: String::new(),
            traces: vec![],
        };

        for trace_dir in fs::read_dir(trip_dir.path()).unwrap() {
            let trace_dir = trace_dir.unwrap();
            let trace_filename = trace_dir.file_name();
            let trace_filename_as_string = trace_filename.to_string_lossy().to_string();

            let file = File::open(trace_dir.path()).unwrap();
            let reader = BufReader::new(file);

            let mut trace = Trace {
                path: format!("{trip_filename}/{trace_filename_as_string}"),
                name: trace_filename_as_string
                    .strip_suffix(".gpx")
                    .unwrap()
                    .to_string(),
                distance: 0.0,
                distance_format: String::new(),
                elevation_negative: 0.0,
                elevation_negative_format: String::new(),
                elevation_positive: 0.0,
                elevation_positive_format: String::new(),
            };

            let gpx = read(reader).unwrap();

            let mut previous_point: Option<&Waypoint> = None;

            for track in &gpx.tracks {
                for segment in &track.segments {
                    for point in &segment.points {
                        if point.hdop.unwrap() > 10.0 {
                            continue;
                        }
                        if let Some(previous_point) = previous_point {
                            let start = Location::new(
                                previous_point.point().y(),
                                previous_point.point().x(),
                            );
                            let end = Location::new(point.point().y(), point.point().x());
                            let distance = start.distance_to(&end).unwrap();
                            if distance.meters() < 5.0 {
                                continue;
                            }

                            trace.distance += distance.meters();
                            trip.distance += distance.meters();

                            let elevation =
                                point.elevation.unwrap() - previous_point.elevation.unwrap();

                            if elevation < -0.5 || elevation > 0.5 {
                                if elevation > 0.0 {
                                    trace.elevation_positive += elevation;
                                    trip.elevation_positive += elevation;
                                } else {
                                    trace.elevation_negative += elevation * -1.0;
                                    trip.elevation_negative += elevation * -1.0;
                                }
                            }
                        }

                        previous_point = Some(point);
                    }
                }
            }

            trace.distance_format = format_meters(trace.distance);
            trace.elevation_positive_format = format_meters(trace.elevation_positive);
            trace.elevation_negative_format = format_meters(trace.elevation_negative);

            trip.traces.push(trace);
        }

        trip.distance_format = format_meters(trip.distance);
        trip.elevation_positive_format = format_meters(trip.elevation_positive);
        trip.elevation_negative_format = format_meters(trip.elevation_negative);

        trips.push(trip);
    }

    trips
}
