mod downloadable_asset;
use downloadable_asset::*;

use log::{debug, info};
use std::path::PathBuf;

const K3D_BIN_FILE_NAME: &str = "k3d";

pub struct K3d {
    base_dir: PathBuf,
}

impl K3d {
    pub fn new(base_dir: PathBuf) -> Self {
        Self { base_dir }
    }

    async fn get_binary_for_current_platform(
        &self,
        latest_release: octocrab::models::repos::Release,
    ) -> DownloadableAsset {
        let os = std::env::consts::OS;
        let arch = std::env::consts::ARCH;

        debug!("Detecting platform: OS={}, ARCH={}", os, arch);

        // 2. Construct the binary name pattern based on platform
        let binary_pattern = match (os, arch) {
            ("linux", "x86") => "k3d-linux-386",
            ("linux", "x86_64") => "k3d-linux-amd64",
            ("linux", "arm") => "k3d-linux-arm",
            ("linux", "aarch64") => "k3d-linux-arm64",
            ("windows", "x86_64") => "k3d-windows-amd64.exe",
            ("macos", "x86_64") => "k3d-darwin-amd64",
            ("macos", "aarch64") => "k3d-darwin-arm64",
            _ => panic!("Unsupported platform: {}-{}", os, arch),
        };

        debug!("Looking for binary matching pattern: {}", binary_pattern);

        // 3. Find the matching binary in release assets
        let binary_asset = latest_release
            .assets
            .iter()
            .find(|asset| asset.name == binary_pattern)
            .unwrap_or_else(|| panic!("No matching binary found for {}", binary_pattern));

        let binary_url = binary_asset.browser_download_url.clone();

        // 4. Find and parse the checksums file
        let checksums_asset = latest_release
            .assets
            .iter()
            .find(|asset| asset.name == "checksums.txt")
            .expect("Checksums file not found in release assets");

        // 5. Download and parse checksums file
        let checksums_url = checksums_asset.browser_download_url.clone();

        let body = reqwest::get(checksums_url)
            .await
            .unwrap()
            .text()
            .await
            .unwrap();
        println!("body: {body}");

        // 6. Find the checksum for our binary
        let checksum = body
            .lines()
            .find_map(|line| {
                if line.ends_with(&binary_pattern) {
                    Some(line.split_whitespace().next().unwrap_or("").to_string())
                } else {
                    None
                }
            })
            .unwrap_or_else(|| panic!("Checksum not found for {}", binary_pattern));

        debug!("Found binary at {} with checksum {}", binary_url, checksum);

        DownloadableAsset {
            url: binary_url,
            file_name: K3D_BIN_FILE_NAME.to_string(),
            checksum,
        }
    }

    pub async fn download_latest_release(&self) -> Result<PathBuf, String> {
        let latest_release = self.get_latest_release_tag().await.unwrap();

        let release_binary = self.get_binary_for_current_platform(latest_release).await;
        info!("Foudn K3d binary to install : {release_binary:#?}");
        release_binary.download_to_path(self.base_dir.clone()).await
    }

    // TODO : Make sure this will only find actual released versions, no prereleases or test
    // builds
    pub async fn get_latest_release_tag(&self) -> Result<octocrab::models::repos::Release, String> {
        let octo = octocrab::instance();
        let latest_release = octo
            .repos("k3d-io", "k3d")
            .releases()
            .get_latest()
            .await
            .map_err(|e| e.to_string())?;
        // debug!("Got k3d releases {releases:#?}");
        println!("Got k3d first releases {latest_release:#?}");

        Ok(latest_release)
    }
}

#[cfg(test)]
mod test {
    use regex::Regex;
    use std::path::PathBuf;

    use crate::{K3d, K3D_BIN_FILE_NAME};

    #[tokio::test]
    async fn k3d_latest_release_should_get_latest() {
        let dir = get_clean_test_directory();

        assert_eq!(dir.join(K3D_BIN_FILE_NAME).exists(), false);

        let k3d = K3d::new(dir.clone());
        let latest_release = k3d.get_latest_release_tag().await.unwrap();

        let tag_regex = Regex::new(r"^v\d+\.\d+\.\d+$").unwrap();
        assert!(tag_regex.is_match(&latest_release.tag_name));
        assert!(!latest_release.tag_name.is_empty());
    }

    #[tokio::test]
    async fn k3d_download_latest_release_should_get_latest_bin() {
        let dir = get_clean_test_directory();

        assert_eq!(dir.join(K3D_BIN_FILE_NAME).exists(), false);

        let k3d = K3d::new(dir.clone());
        let bin_file_path = k3d.download_latest_release().await.unwrap();
        assert_eq!(bin_file_path, dir.join(K3D_BIN_FILE_NAME));
        assert_eq!(dir.join(K3D_BIN_FILE_NAME).exists(), true);
    }

    fn get_clean_test_directory() -> PathBuf {
        let dir = PathBuf::from("/tmp/harmony-k3d-test-dir");

        if dir.exists() {
            if let Err(e) = std::fs::remove_dir_all(&dir) {
                // TODO sometimes this fails because of the race when running multiple tests at
                // once
                panic!("Failed to clean up test directory: {}", e);
            }
        }

        if let Err(e) = std::fs::create_dir_all(&dir) {
            panic!("Failed to create test directory: {}", e);
        }

        dir
    }
}
