use anyhow::{anyhow, Result};
use chrono::{DateTime, Utc};
use core::fmt;
use global_placeholders::global;
use macros_rs::crashln;
use pmc::{file::Exists, helpers};
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, fs, io};

#[derive(Copy, Clone, Serialize, Deserialize)]
pub struct Pid(i32);

impl Pid {
    pub fn get<T>(&self) -> T
    where
        T: TryFrom<i32>,
        T::Error: std::fmt::Debug,
    {
        T::try_from(self.0).unwrap()
    }
}

impl fmt::Display for Pid {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) }
}

pub fn exists() -> bool { fs::metadata(global!("pmc.pid")).is_ok() }
pub fn running(pid: i32) -> bool { unsafe { libc::kill(pid, 0) == 0 } }

pub fn uptime() -> io::Result<DateTime<Utc>> {
    let metadata = fs::metadata(global!("pmc.pid"))?;
    let creation_time = metadata.created()?;
    let creation_time = DateTime::from(creation_time);

    Ok(creation_time)
}

pub fn read() -> Result<Pid> {
    let pid = fs::read_to_string(global!("pmc.pid")).map_err(|err| anyhow!(err))?;

    let trimmed_pid = pid.trim();
    let parsed_pid = trimmed_pid.parse::<i32>().map_err(|err| anyhow!(err))?;

    Ok(Pid(parsed_pid))
}

pub fn write(pid: u32) {
    if let Err(err) = fs::write(global!("pmc.pid"), pid.to_string()) {
        crashln!("{} Failed to write PID to file: {}", *helpers::FAIL, err);
    }
}

pub fn remove() {
    if Exists::check(&global!("pmc.pid")).file() {
        log::warn!("Stale PID file detected. Removing the PID file.");
        if let Err(err) = fs::remove_file(global!("pmc.pid")) {
            log::error!("Failed to remove PID file: {}", err);
        }
    } else {
        log::info!("No Stale PID file detected.");
    }
}

#[cfg(target_os = "linux")]
pub fn name(new_name: &str) {
    use std::ffi::CString;

    let pid = std::process::id() as libc::pid_t;
    let name_cstr = CString::new(new_name).expect("Failed to convert name to CString");

    unsafe {
        libc::setpgid(pid, 0);
        libc::prctl(libc::PR_SET_NAME, name_cstr.as_ptr() as libc::c_ulong, 0, 0, 0);
    }
}

#[cfg(target_os = "macos")]
pub fn name(new_name: &str) {
    use std::ffi::CString;

    let pid = std::process::id() as libc::pid_t;
    let name_cstr = CString::new(new_name).expect("Failed to convert name to CString");

    unsafe {
        libc::setpgid(pid, 0);
        libc::pthread_setname_np(name_cstr.as_ptr());
    }
}