/* update_check.rs
 *
 * Copyright 2026 Christian Kruse
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

use std::time::{Duration, SystemTime, UNIX_EPOCH};

use adw::prelude::*;

use crate::config::VERSION;
use crate::portal;
use crate::preferences::app_settings;

const FLATHUB_ENDPOINT: &str = "https://flathub.org/api/v2/appstream/de.wwwtech.gitte";
const CHECK_INTERVAL_SECS: i64 = 24 * 60 * 60;

pub fn should_check() -> bool {
    if portal::is_flatpak() {
        return false;
    }

    let now = unix_now();
    let last = app_settings().int64("last-update-check");
    now.saturating_sub(last) >= CHECK_INTERVAL_SECS
}

pub fn record_check_done() {
    let _ = app_settings().set_int64("last-update-check", unix_now());
}

pub fn dismissed_version() -> String {
    app_settings().string("dismissed-update-version").to_string()
}

pub fn set_dismissed_version(version: &str) {
    let _ = app_settings().set_string("dismissed-update-version", version);
}

pub fn fetch_latest_version() -> Option<String> {
    let agent: ureq::Agent = ureq::Agent::config_builder()
        .timeout_global(Some(Duration::from_secs(10)))
        .user_agent(format!("gitte/{VERSION}"))
        .build()
        .into();

    let mut response = match agent.get(FLATHUB_ENDPOINT).call() {
        Ok(r) => r,
        Err(e) => {
            tracing::warn!("update-check: HTTP request failed: {e}");
            return None;
        }
    };

    let value: serde_json::Value = match response.body_mut().read_json() {
        Ok(v) => v,
        Err(e) => {
            tracing::warn!("update-check: failed to parse JSON: {e}");
            return None;
        }
    };

    let version = value["releases"][0]["version"].as_str().map(|s| s.to_string());

    if version.is_none() {
        tracing::warn!("update-check: response did not contain releases[0].version");
    }

    version
}

pub fn is_newer(current: &str, candidate: &str) -> bool {
    let cur = parse_version(current);
    let cand = parse_version(candidate);

    match (cur, cand) {
        (Some(a), Some(b)) => b > a,
        _ => candidate != current && candidate > current,
    }
}

fn parse_version(s: &str) -> Option<Vec<u32>> {
    let trimmed = s.trim().trim_start_matches('v');
    let parts: Vec<u32> = trimmed
        .split('.')
        .map(|p| p.parse::<u32>().ok())
        .collect::<Option<Vec<_>>>()?;

    if parts.is_empty() { None } else { Some(parts) }
}

fn unix_now() -> i64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_secs() as i64)
        .unwrap_or(0)
}

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

    #[test]
    fn newer_versions_detected() {
        assert!(is_newer("0.2.0", "0.2.1"));
        assert!(is_newer("0.2.0", "0.3.0"));
        assert!(is_newer("0.2.0", "1.0.0"));
        assert!(is_newer("0.2.9", "0.2.10"));
    }

    #[test]
    fn equal_or_older_not_newer() {
        assert!(!is_newer("0.2.0", "0.2.0"));
        assert!(!is_newer("0.3.0", "0.2.9"));
        assert!(!is_newer("1.0.0", "0.99.0"));
    }
}
