use std::net::SocketAddr;
use std::time::UNIX_EPOCH;
use std::{error::Error, time::SystemTime};

use chrono::DateTime;
use gpsd_proto::{Tpv, UnifiedResponse};
use tokio::{
    io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
    net::TcpStream,
    sync::mpsc::UnboundedSender,
};
use tracing::{debug, info, trace};

#[derive(Debug)]
pub struct GpsData {
    pub timestamp: u64,
    pub lat: Option<f64>,
    pub lon: Option<f64>,
}

pub async fn run_gpsd_eval(
    sender: UnboundedSender<Option<GpsData>>,
    testing: bool,
) -> Result<(), Box<dyn Error>> {
    if !testing {
        let addr: SocketAddr = "127.0.0.1:2947".parse().unwrap();

        let mut stream = TcpStream::connect(addr).await?;
        debug!("Connected to server: {}", stream.peer_addr()?);

        stream
            .write_all(gpsd_proto::ENABLE_WATCH_CMD.as_bytes())
            .await?;

        let mut stream_reader = BufReader::new(stream).lines();

        while let Some(line) = stream_reader.next_line().await.unwrap() {
            if let Ok(rd) = serde_json::from_str(&line) {
                match rd {
                    UnifiedResponse::Version(v) => {
                        if v.proto_major < gpsd_proto::PROTO_MAJOR_MIN {
                            panic!("Gpsd major version mismatch");
                        }
                        info!("Gpsd version {} connected", v.rev);
                    }
                    UnifiedResponse::Device(d) => debug!("Device {:?}", d),
                    UnifiedResponse::Tpv(t) => {
                        let data = parse_tpv(t);
                        if let Some(data) = data {
                            sender
                                .send(Some(data))
                                .expect("Couldn't send GpsData to main thread")
                        }
                    }
                    _ => {}
                }
            };
        }
        Ok(())
    } else {
        loop {
            sender
                .send(Some(GpsData {
                    timestamp: SystemTime::now()
                        .duration_since(UNIX_EPOCH)
                        .expect("Time went backwards.")
                        .as_secs(),
                    lat: Some(0_f64),
                    lon: Some(0_f64),
                }))
                .expect("Couldn't send GpsData to main thread");
            tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
        }
        #[allow(unreachable_code)]
        Ok(())
    }
}

fn parse_tpv(t: Tpv) -> Option<GpsData> {
    if let Some(time) = t.time {
        let timestamp = DateTime::parse_from_rfc3339(&time)
            .unwrap()
            .timestamp()
            .unsigned_abs();
        let lat = t.lat;
        let lon = t.lon;
        trace!("TPV: t: {}, lat: {:?}, lon: {:?}", timestamp, lat, lon);
        Some(GpsData {
            timestamp,
            lat,
            lon,
        })
    } else {
        None
    }
}
