use anyhow::{anyhow, Context, Result};
use chrono::{DateTime, Utc};
#[cfg(feature = "tls")]
use rustls_pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer};
use std::{
    borrow::Cow,
    path::Path,
    time::{Duration, SystemTime, UNIX_EPOCH},
};

pub fn unix_now() -> Duration {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("Unable to get unix epoch time")
}

pub fn encode_uri(v: &str) -> String {
    let parts: Vec<_> = v.split('/').map(urlencoding::encode).collect();
    parts.join("/")
}

pub fn decode_uri(v: &str) -> Option<Cow<'_, str>> {
    percent_encoding::percent_decode(v.as_bytes())
        .decode_utf8()
        .ok()
}

pub fn get_file_name(path: &Path) -> &str {
    path.file_name()
        .and_then(|v| v.to_str())
        .unwrap_or_default()
}

#[cfg(unix)]
pub async fn get_file_mtime_and_mode(path: &Path) -> Result<(DateTime<Utc>, u16)> {
    use std::os::unix::prelude::MetadataExt;
    let meta = tokio::fs::metadata(path).await?;
    let datetime: DateTime<Utc> = meta.modified()?.into();
    Ok((datetime, meta.mode() as u16))
}

#[cfg(not(unix))]
pub async fn get_file_mtime_and_mode(path: &Path) -> Result<(DateTime<Utc>, u16)> {
    let meta = tokio::fs::metadata(&path).await?;
    let datetime: DateTime<Utc> = meta.modified()?.into();
    Ok((datetime, 0o644))
}

pub fn try_get_file_name(path: &Path) -> Result<&str> {
    path.file_name()
        .and_then(|v| v.to_str())
        .ok_or_else(|| anyhow!("Failed to get file name of `{}`", path.display()))
}

pub fn glob(pattern: &str, target: &str) -> bool {
    let pat = match ::glob::Pattern::new(pattern) {
        Ok(pat) => pat,
        Err(_) => return false,
    };
    pat.matches(target)
}

// Load public certificate from file.
#[cfg(feature = "tls")]
pub fn load_certs<T: AsRef<Path>>(file_name: T) -> Result<Vec<CertificateDer<'static>>> {
    let mut certs = vec![];
    for cert in CertificateDer::pem_file_iter(file_name.as_ref()).with_context(|| {
        format!(
            "Failed to load cert file at `{}`",
            file_name.as_ref().display()
        )
    })? {
        let cert = cert.with_context(|| {
            format!(
                "Invalid certificate data in file `{}`",
                file_name.as_ref().display()
            )
        })?;
        certs.push(cert)
    }
    if certs.is_empty() {
        anyhow::bail!(
            "No supported certificate in file `{}`",
            file_name.as_ref().display()
        );
    }
    Ok(certs)
}

// Load private key from file.
#[cfg(feature = "tls")]
pub fn load_private_key<T: AsRef<Path>>(file_name: T) -> Result<PrivateKeyDer<'static>> {
    PrivateKeyDer::from_pem_file(file_name.as_ref()).with_context(|| {
        format!(
            "Failed to load key file at `{}`",
            file_name.as_ref().display()
        )
    })
}

pub fn parse_range(range: &str, size: u64) -> Option<Vec<(u64, u64)>> {
    let (unit, ranges) = range.split_once('=')?;
    if unit != "bytes" {
        return None;
    }

    let mut result = Vec::new();
    for range in ranges.split(',') {
        let (start, end) = range.trim().split_once('-')?;
        if start.is_empty() {
            let offset = end.parse::<u64>().ok()?;
            if offset <= size {
                result.push((size - offset, size - 1));
            } else {
                return None;
            }
        } else {
            let start = start.parse::<u64>().ok()?;
            if start < size {
                if end.is_empty() {
                    result.push((start, size - 1));
                } else {
                    let end = end.parse::<u64>().ok()?;
                    if end < size && start <= end {
                        result.push((start, end));
                    } else {
                        return None;
                    }
                }
            } else {
                return None;
            }
        }
    }

    Some(result)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_glob_key() {
        assert!(glob("", ""));
        assert!(glob(".*", ".git"));
        assert!(glob("abc", "abc"));
        assert!(glob("a*c", "abc"));
        assert!(glob("a?c", "abc"));
        assert!(glob("a*c", "abbc"));
        assert!(glob("*c", "abc"));
        assert!(glob("a*", "abc"));
        assert!(glob("?c", "bc"));
        assert!(glob("a?", "ab"));
        assert!(!glob("abc", "adc"));
        assert!(!glob("abc", "abcd"));
        assert!(!glob("a?c", "abbc"));
        assert!(!glob("*.log", "log"));
        assert!(glob("*.abc-cba", "xyz.abc-cba"));
        assert!(glob("*.abc-cba", "123.xyz.abc-cba"));
        assert!(glob("*.log", ".log"));
        assert!(glob("*.log", "a.log"));
        assert!(glob("*/", "abc/"));
        assert!(!glob("*/", "abc"));
    }

    #[test]
    fn test_parse_range() {
        assert_eq!(parse_range("bytes=0-499", 500), Some(vec![(0, 499)]));
        assert_eq!(parse_range("bytes=0-", 500), Some(vec![(0, 499)]));
        assert_eq!(parse_range("bytes=299-", 500), Some(vec![(299, 499)]));
        assert_eq!(parse_range("bytes=-500", 500), Some(vec![(0, 499)]));
        assert_eq!(parse_range("bytes=-300", 500), Some(vec![(200, 499)]));
        assert_eq!(
            parse_range("bytes=0-199, 100-399, 400-, -200", 500),
            Some(vec![(0, 199), (100, 399), (400, 499), (300, 499)])
        );
        assert_eq!(parse_range("bytes=500-", 500), None);
        assert_eq!(parse_range("bytes=-501", 500), None);
        assert_eq!(parse_range("bytes=0-500", 500), None);
        assert_eq!(parse_range("bytes=0-199,", 500), None);
        assert_eq!(parse_range("bytes=0-199, 500-", 500), None);
    }
}
