use sha2::{Digest, Sha256};
use std::fmt::{Display, Formatter};
use std::fs::{self};
use std::io::Write;
use std::path::PathBuf;

use anyhow::{Result, anyhow};
use semver::Version;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct CacheData {
    version: String,
    ttl: i64,
}

impl Display for CacheData {
    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
        write!(
            f,
            "{{\"ttl\": \"{}\", \"version\": \"{}\"}}",
            self.ttl, self.version
        )
    }
}

pub fn parse_version(s: &str) -> Result<Version> {
    log::debug!("Parsing: {s}");
    Version::parse(s.trim_matches('"').trim_start_matches('v'))
        .map_err(|e| anyhow!("Invalid version: {}", e))
}

pub fn cache_lookup_and_ttl_check(path: &PathBuf) -> Option<Version> {
    if let Some((version, ttl)) = cache_lookup(&path) {
        if is_ttl_valid(ttl) {
            Some(version)
        } else {
            None
        }
    } else {
        None
    }
}

pub fn cache_lookup(path: &PathBuf) -> Option<(Version, i64)> {
    let result = fs::read_to_string(path);
    if result.is_err() {
        log::debug!("Cache miss on [{}]", path.display());
    }
    let content = result.ok()?;
    let cache_data: CacheData = serde_json::from_str(&content).unwrap_or_else(|err| {
        log::error!("Error parsing cache file [{}]: {}", path.display(), err);
        None
    })?;
    let version_str = cache_data.version;
    let version = parse_version(&version_str).ok()?;
    let ttl = cache_data.ttl;
    log::debug!("Cache hit [{}] ", path.display());
    Some((version, ttl))
}

pub fn is_ttl_valid(ttl: i64) -> bool {
    let current_time = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs() as i64;
    let isvalid = current_time < ttl;
    log::debug!("TTL is {}", if isvalid { "valid" } else { "expired" });
    isvalid
}

pub fn cache_write(path: &PathBuf, version: &Version) -> Result<()> {
    let current_time = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs() as i64;
    let ttl = current_time + (6 * 60 * 60); // 6 hours
    let cache_data = CacheData {
        version: version.to_string(),
        ttl,
    };
    let json = serde_json::to_string_pretty(&cache_data)?;

    let mut file = fs::File::create(path)?;
    file.write_all(json.as_bytes())?;
    log::debug!("Wrote [{}] to cache file [{:?}].", &cache_data, &file);

    Ok(())
}

pub fn hash_kubeconfig_to_filename(kubeconfig: &PathBuf) -> Result<String> {
    let mut hasher = Sha256::new();
    let contents = fs::read_to_string(kubeconfig).unwrap_or("".to_owned());
    // let re = Regex::new(r"(?m)^.*\b(cert|key)\b.*\n?").unwrap();
    // let re = Regex::new(r".*\b(cert|key)\b.*").unwrap();
    // let redacted_contents = re.replace_all(&contents, "").to_string();
    // log::debug!("Kubeconfig redacted contents [{}]", redacted_contents);
    // TODO: redact or just don't log out the kubeconfig contents
    // log::debug!("Kubeconfig contents: [{}]", contents);
    log::debug!("Kubeconfig contents: [redacted]");

    hasher.update(contents.as_bytes());
    let hash = format!("{:x}", hasher.finalize())[..9].to_string();
    let filename = format!("kubeconfig-{hash}");
    Ok(filename)
}
