use std::io;
use std::process::{Command, Output};

use crate::error::PortKillerError;
use crate::models::ProcessInfo;
use crate::parser::parse_windows_netstat_output;
use crate::Result;

pub fn find_processes_by_port(port: u16) -> Result<Vec<ProcessInfo>> {
    let output = Command::new("netstat")
        .args(["-ano", "-p", "TCP"])
        .output()
        .map_err(|source| map_command_error("netstat", source))?;

    if !output.status.success() && output.stdout.is_empty() {
        return Ok(Vec::new());
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    Ok(with_process_details(parse_windows_netstat_output(
        &stdout, port,
    )))
}

pub fn kill_process(pid: u32, force: bool) -> Result<()> {
    let mut command = Command::new("taskkill");
    let pid_text = pid.to_string();
    command.args(["/PID", &pid_text, "/F"]);

    if force {
        command.arg("/T");
    }

    let output = command
        .output()
        .map_err(|source| map_command_error("taskkill", source))?;

    if output.status.success() {
        Ok(())
    } else {
        let reason = command_failure_reason(&output);

        if command_failed_with_permission_error(&reason) {
            Err(PortKillerError::PermissionDenied { pid })
        } else {
            Err(PortKillerError::KillFailed { pid, reason })
        }
    }
}

#[derive(Debug)]
struct ProcessDetails {
    name: String,
    command: String,
}

fn process_details(pid: u32) -> Option<ProcessDetails> {
    let name = powershell_output(&format!(
        "Get-CimInstance Win32_Process -Filter \"ProcessId = {pid}\" | Select-Object -First 1 -ExpandProperty Name"
    ))?;
    let command = powershell_output(&format!(
        "Get-CimInstance Win32_Process -Filter \"ProcessId = {pid}\" | Select-Object -First 1 -ExpandProperty CommandLine"
    ))
    .unwrap_or_else(|| name.clone());

    Some(ProcessDetails { name, command })
}

fn with_process_details(mut processes: Vec<ProcessInfo>) -> Vec<ProcessInfo> {
    for process in &mut processes {
        if let Some(details) = process_details(process.pid) {
            if !details.name.is_empty() {
                process.name = details.name;
            }

            if !details.command.is_empty() {
                process.command = details.command;
            }
        }
    }

    processes
}

fn powershell_output(script: &str) -> Option<String> {
    let output = run_powershell("powershell", script)
        .or_else(|_| run_powershell("pwsh", script))
        .ok()?;

    if !output.status.success() {
        return None;
    }

    let text = String::from_utf8_lossy(&output.stdout).trim().to_string();
    (!text.is_empty()).then_some(text)
}

fn run_powershell(program: &str, script: &str) -> io::Result<Output> {
    Command::new(program)
        .args(["-NoProfile", "-NonInteractive", "-Command", script])
        .output()
}

fn map_command_error(command: &str, source: io::Error) -> PortKillerError {
    if source.kind() == io::ErrorKind::NotFound {
        PortKillerError::CommandNotFound(command.to_string())
    } else {
        PortKillerError::CommandIo {
            command: command.to_string(),
            source,
        }
    }
}

fn command_failure_reason(output: &std::process::Output) -> String {
    let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
    let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();

    if !stderr.is_empty() {
        stderr
    } else if !stdout.is_empty() {
        stdout
    } else {
        output.status.to_string()
    }
}

fn command_failed_with_permission_error(reason: &str) -> bool {
    let text = reason.to_ascii_lowercase();
    text.contains("access is denied") || text.contains("permission denied")
}
