use base64::Engine;

use super::super::models::types::{ExecRequest, SystemProfile};
use super::{
    normalize_optional, RemoteShell, SystemProfileCommands, SystemProfileOutputs, SystemType,
};

pub(crate) struct WindowsPowerShell;

impl RemoteShell for WindowsPowerShell {
    fn system_type(&self) -> SystemType {
        SystemType::Windows
    }

    fn build_exec_command(&self, req: &ExecRequest) -> String {
        let needs_shell = req.use_shell || req.cwd.is_some() || !req.env.is_empty() || req.sudo;
        if !needs_shell {
            return format!(
                "powershell -Command \"& {{ {} }}\"",
                ps_escape_double_quotes(&req.command)
            );
        }

        let mut script = String::new();

        for (key, value) in &req.env {
            script.push_str(&format!(
                "$env:{}='{}';\n",
                key,
                ps_escape_single_quotes(value)
            ));
        }

        if let Some(cwd) = req.cwd.as_deref() {
            script.push_str(&format!(
                "Set-Location '{}';\n",
                ps_escape_single_quotes(cwd)
            ));
        }

        script.push_str(&req.command);

        if req.sudo {
            script = format!(
                "Start-Process powershell -Verb RunAs -Wait -ArgumentList '-EncodedCommand {}'",
                ps_encode_command(&script)
            );
        }

        format!("powershell -EncodedCommand {}", ps_encode_command(&script))
    }

    fn build_system_profile_commands(&self) -> SystemProfileCommands {
        SystemProfileCommands {
            os_info: ExecRequest {
                command: "powershell -EncodedCommand"
                    .to_string()
                    + &ps_encode_command("(Get-CimInstance Win32_OperatingSystem).Caption"),
                use_shell: false,
                ..ExecRequest::default()
            },
            arch: ExecRequest {
                command: "powershell -EncodedCommand"
                    .to_string()
                    + &ps_encode_command("Write-Host $env:PROCESSOR_ARCHITECTURE"),
                use_shell: false,
                ..ExecRequest::default()
            },
            shell: ExecRequest {
                command: "powershell -EncodedCommand"
                    .to_string()
                    + &ps_encode_command("Write-Host $env:ComSpec"),
                use_shell: false,
                ..ExecRequest::default()
            },
            package_manager: ExecRequest {
                command: "powershell -EncodedCommand".to_string()
                    + &ps_encode_command(
                        "if (Get-Command winget -ErrorAction SilentlyContinue) { Write-Host 'winget' } elseif (Get-Command choco -ErrorAction SilentlyContinue) { Write-Host 'choco' } else { Write-Host '' }",
                    ),
                use_shell: false,
                ..ExecRequest::default()
            },
        }
    }

    fn parse_system_profile(&self, outputs: &SystemProfileOutputs) -> SystemProfile {
        let arch = outputs.arch.trim();
        let shell_path = outputs.shell.trim();
        let pm = outputs.package_manager.trim();

        SystemProfile {
            os_id: Some("windows".to_string()),
            os_version: normalize_optional(&outputs.os_info),
            arch: if arch.is_empty() {
                None
            } else {
                Some(arch.to_string())
            },
            shell: if shell_path.is_empty() {
                None
            } else {
                Some(shell_path.to_string())
            },
            package_manager: if pm.is_empty() {
                None
            } else {
                Some(pm.to_string())
            },
        }
    }
}

fn ps_encode_command(script: &str) -> String {
    let utf16: Vec<u8> = script
        .encode_utf16()
        .flat_map(|c| c.to_le_bytes())
        .collect();
    base64::engine::general_purpose::STANDARD.encode(&utf16)
}

fn ps_escape_single_quotes(s: &str) -> String {
    s.replace('\'', "''")
}

fn ps_escape_double_quotes(s: &str) -> String {
    s.replace('"', "`\"")
}
