use anyhow::{Context, Result};
use semver::Version;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use vtcode_core::utils::file_utils::{ensure_dir_exists_sync, write_file_with_context_sync};

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(super) struct UpdateCacheSnapshot {
    pub(super) last_checked: Option<SystemTime>,
    pub(super) latest_version: Option<Version>,
    pub(super) latest_was_newer: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct UpdateCachePayload {
    last_checked_unix_secs: u64,
    #[serde(default)]
    latest_version: Option<String>,
    #[serde(default)]
    latest_was_newer: bool,
}

pub(super) fn read_snapshot() -> Result<UpdateCacheSnapshot> {
    let cache_file = cache_file_path()?;
    if !cache_file.exists() {
        return Ok(UpdateCacheSnapshot::default());
    }

    let metadata = std::fs::metadata(&cache_file).with_context(|| {
        format!(
            "Failed to read update cache metadata {}",
            cache_file.display()
        )
    })?;
    let modified = metadata.modified().ok();

    let Ok(content) = std::fs::read_to_string(&cache_file) else {
        return Ok(UpdateCacheSnapshot {
            last_checked: modified,
            latest_version: None,
            latest_was_newer: false,
        });
    };

    let trimmed = content.trim();
    if trimmed.is_empty() {
        return Ok(UpdateCacheSnapshot {
            last_checked: modified,
            latest_version: None,
            latest_was_newer: false,
        });
    }

    let Ok(payload) = serde_json::from_str::<UpdateCachePayload>(trimmed) else {
        return Ok(UpdateCacheSnapshot {
            last_checked: modified,
            latest_version: None,
            latest_was_newer: false,
        });
    };

    Ok(UpdateCacheSnapshot {
        last_checked: payload
            .last_checked_unix_secs
            .checked_add(0)
            .map(|secs| UNIX_EPOCH + std::time::Duration::from_secs(secs))
            .or(modified),
        latest_version: payload
            .latest_version
            .as_deref()
            .and_then(|value| Version::parse(value).ok()),
        latest_was_newer: payload.latest_was_newer,
    })
}

pub(super) fn record_successful_check(
    latest_version: Option<&Version>,
    latest_was_newer: bool,
) -> Result<()> {
    write_snapshot(UpdateCacheSnapshot {
        last_checked: Some(SystemTime::now()),
        latest_version: latest_version.cloned(),
        latest_was_newer,
    })
}

pub(super) fn record_failed_check() -> Result<()> {
    let mut snapshot = read_snapshot()?;
    snapshot.last_checked = Some(SystemTime::now());
    write_snapshot(snapshot)
}

fn write_snapshot(snapshot: UpdateCacheSnapshot) -> Result<()> {
    let last_checked = snapshot.last_checked.unwrap_or_else(SystemTime::now);
    let last_checked_unix_secs = last_checked
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs();
    let payload = UpdateCachePayload {
        last_checked_unix_secs,
        latest_version: snapshot.latest_version.map(|version| version.to_string()),
        latest_was_newer: snapshot.latest_was_newer,
    };
    let serialized =
        serde_json::to_string(&payload).context("Failed to serialize update cache payload")?;
    write_file_with_context_sync(&cache_file_path()?, &serialized, "update cache")
        .context("Failed to write update cache")?;
    Ok(())
}

fn get_cache_dir() -> Result<PathBuf> {
    let dir = if let Ok(xdg_cache) = std::env::var("XDG_CACHE_HOME") {
        PathBuf::from(xdg_cache).join("vtcode")
    } else {
        let home = dirs::home_dir().context("Cannot determine home directory")?;
        home.join(".cache/vtcode")
    };

    ensure_dir_exists_sync(&dir).context("Failed to create cache directory")?;
    Ok(dir)
}

fn cache_file_path() -> Result<PathBuf> {
    Ok(get_cache_dir()?.join("last_update_check"))
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::env;
    use tempfile::TempDir;
    use vtcode_commons::env_lock;

    #[test]
    fn empty_legacy_cache_file_uses_file_metadata() {
        let env_guard = env_lock::lock();
        let temp_dir = TempDir::new().expect("temp dir");
        let previous = env::var_os("XDG_CACHE_HOME");
        env_guard.set_var("XDG_CACHE_HOME", temp_dir.path());

        let cache_file = cache_file_path().expect("cache path");
        std::fs::write(&cache_file, "").expect("write legacy cache");

        let snapshot = read_snapshot().expect("read snapshot");
        assert!(snapshot.last_checked.is_some());
        assert!(snapshot.latest_version.is_none());
        assert!(!snapshot.latest_was_newer);

        env_guard.restore_var("XDG_CACHE_HOME", previous);
    }

    #[test]
    fn json_cache_round_trips_latest_version() {
        let env_guard = env_lock::lock();
        let temp_dir = TempDir::new().expect("temp dir");
        let previous = env::var_os("XDG_CACHE_HOME");
        env_guard.set_var("XDG_CACHE_HOME", temp_dir.path());

        let version = Version::parse("0.113.0").expect("version");
        record_successful_check(Some(&version), true).expect("write cache");

        let snapshot = read_snapshot().expect("read snapshot");
        assert_eq!(snapshot.latest_version, Some(version));
        assert!(snapshot.latest_was_newer);
        assert!(snapshot.last_checked.is_some());

        env_guard.restore_var("XDG_CACHE_HOME", previous);
    }
}
