use core::fmt;
use std::{
    process::Command,
    time::{Duration, SystemTime},
};

use crate::BString;

#[derive(Clone)]
pub struct DateTime {
    sys: SystemTime,
}

impl DateTime {
    pub fn from_unix(unix: u64) -> Self {
        Self {
            sys: SystemTime::UNIX_EPOCH + Duration::from_secs(unix),
        }
    }
    pub fn now() -> Self {
        Self {
            sys: SystemTime::now(),
        }
    }
    fn format(&self) -> std::io::Result<BString> {
        let mut out = Command::new("date")
            .arg("-d")
            .arg(format!(
                "@{}",
                self.sys
                    .duration_since(SystemTime::UNIX_EPOCH)
                    .unwrap()
                    .as_secs()
            ))
            .output()?
            .stdout;
        while let Some(x) = out.last()
            && x.is_ascii_whitespace()
        {
            out.pop();
        }
        Ok(out)
    }

    pub fn unix(&self) -> u64 {
        self.sys
            .duration_since(SystemTime::UNIX_EPOCH)
            .unwrap()
            .as_secs()
    }

    #[allow(clippy::blocks_in_conditions)]
    pub fn relative_to(&self, other: &Self) -> String {
        let a = self.unix();
        let b = other.unix();

        let (pre, post, mut diff) = if a < b {
            ("in ", "", b - a)
        } else {
            ("", " ago", a - b)
        };

        let unit = if diff < 60 {
            "s"
        } else if {
            diff /= 60;
            diff < 60
        } {
            "m"
        } else if {
            diff /= 60;
            diff < 24
        } {
            "h"
        } else if {
            diff /= 24;
            diff < 365
        } {
            "d"
        } else {
            diff /= 365;
            "y"
        };

        format!("{pre}{diff}{unit}{post}")
    }

    pub const fn longest_reasonable_delta() -> usize {
        8
    }
}

#[test]
fn long_delta() {
    assert!(
        DateTime::now()
            .relative_to(&DateTime {
                // 30 years ago
                sys: SystemTime::now() - Duration::from_secs(60 * 60 * 24 * 365 * 30)
            })
            .len()
            <= DateTime::longest_reasonable_delta()
    );
}

#[test]
fn long_delta_2() {
    assert!(
        DateTime::now()
            .relative_to(&DateTime {
                // 200 days ago ago
                sys: SystemTime::now() - Duration::from_secs(60 * 60 * 24 * 200)
            })
            .len()
            <= DateTime::longest_reasonable_delta()
    );
}

impl fmt::Display for DateTime {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            String::from_utf8_lossy(&self.format().unwrap_or_else(|_| Vec::new()))
        )
    }
}
