use std::{
    fs::File,
    io::Write,
    process::{exit, Stdio},
};

use reqwest::Client;
use simplelog::{debug, error, info};
use tokio::process::Command;

pub const DEFAULT_TAILWIND_COMMAND: &str = "tailwindcss";
pub const DEFAULT_TAILWIND_VERSION: &str = "4.1.17";

#[derive(Clone)]
pub struct Asset {
    pub input: String,
    pub output: String,
}

pub struct Tailwind {
    pub collection: Vec<Asset>,
    pub command: String,
}

impl Tailwind {
    // Runs tailwindcss on a single input and output
    async fn execute_cmd(&self, input: &str, output: &str) {
        Command::new(&self.command)
            .args(["-m", "-i", input, "-o", output])
            .stdout(Stdio::null())
            .output()
            .await
            .expect("Failed to execute tailwindcss command.");
    }

    // Generates TailwindCSS download URL for specified version
    // Attribution: https://github.com/trunk-rs/trunk/blob/fff0072dd69ac9fb1d1e526783d5adc4e8845a51/src/tools.rs#L133-L138
    fn generate_download_url(version: &str) -> String {
        let target_os = if cfg!(target_os = "windows") {
            "windows"
        } else if cfg!(target_os = "macos") {
            "macos"
        } else if cfg!(target_os = "linux") {
            "linux"
        } else {
            error!("Unsupported OS");
            exit(1);
        };

        let target_arch = if cfg!(target_arch = "x86_64") {
            "x86_64"
        } else if cfg!(target_arch = "aarch64") {
            "aarch64"
        } else {
            error!("Unsupported target architecture");
            exit(1);
        };

        match (target_os, target_arch) {
            ("windows", "x86_64") => format!("https://github.com/tailwindlabs/tailwindcss/releases/download/v{version}/tailwindcss-windows-x64.exe"),
            ("macos" | "linux", "x86_64") => format!("https://github.com/tailwindlabs/tailwindcss/releases/download/v{version}/tailwindcss-{target_os}-x64"),
            ("macos" | "linux", "aarch64") => format!("https://github.com/tailwindlabs/tailwindcss/releases/download/v{version}/tailwindcss-{target_os}-arm64"),
            _ => {
                error!("Unable to download tailwindcss for {target_os} {target_arch}");
                exit(1);
            }
        }
    }

    /// fetch specified version of tailwindcss release version and sets command for instance
    pub async fn fetch_release_version(tmp_dir: &str, ver: Option<String>) -> String {
        let version = ver.unwrap_or(String::from(DEFAULT_TAILWIND_VERSION));

        // check if executable already exists
        let command = format!("{}/{}-{}", tmp_dir, DEFAULT_TAILWIND_COMMAND, &version);
        if Self::is_installed(&command).await {
            // TailwindCSS executable already exists, return path;
            debug!("TailwindCSS executable already exists at {}", &command);
            return command;
        } else {
            info!("Downloading TailwindCSS v{}...", &version);
        }

        // download tailwindcss
        let client = Client::new();
        let url = Self::generate_download_url(&version);
        match client.get(&url).send().await {
            Ok(response) => {
                // check if download was successful
                if !response.status().is_success() {
                    error!("Failed to download TailwindCSS v{}.", &version);
                    debug!("Attempted downloading from url:\n{}", &url);
                    exit(1);
                }

                // write to temp dir
                File::create(&command)
                    .unwrap()
                    .write_all(&response.bytes().await.unwrap())
                    .unwrap();

                // update permissions for tmp cmd
                #[cfg(unix)]
                {
                    use std::{
                        fs::{set_permissions, Permissions},
                        os::unix::fs::PermissionsExt,
                    };
                    set_permissions(&command, Permissions::from_mode(0o755)).unwrap();
                }
                // #[cfg(not(unix))]
                // {
                //     todo!();
                // }

                // return command to temp path
                command
            }
            Err(err) => {
                error!(
                    "Error while retrieving remote binary for TailwindCSS v{}.",
                    &version
                );
                debug!("Error: {}", err);
                exit(1);
            }
        }
    }

    /// Checks if tailwindcss is installed
    pub async fn is_installed(command: &str) -> bool {
        Command::new(command)
            .output()
            .await
            .map(|output| output.status.success())
            .unwrap_or(false)
    }

    /// Runs tailwindcss against all collected Tailwind assets
    pub async fn run(&self) {
        for run in &self.collection {
            self.execute_cmd(&run.input, &run.output).await;
        }
    }
}

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

    #[tokio::test]
    async fn test_tailwind_fetch_release_version() {
        use tempfile::tempdir;

        let tmp_dir = tempdir().unwrap();
        let tmp_path = tmp_dir.path().to_str().unwrap();

        // check if tailwind executable exists
        let command = Tailwind::fetch_release_version(tmp_path, None).await;
        assert!(Tailwind::is_installed(&command).await);
    }
}
