use crate::options::structs::{CustomError, Files};
use crate::utils::prcs::{is_active, is_frozen};
use inotify::{EventMask, Inotify, WatchMask};
use std::borrow::BorrowMut;
use std::path::Path;
use std::sync::Arc;
use tokio::sync::mpsc;
use tokio::time::Duration;

/// # Fn `create_watcher`
/// ## for creating watcher on file's delete | update events 
/// 
/// *input*      : `&str`, `&str`
///
/// *output*     : `Err` if it cant create file watcher | `Ok(watcher)` on successfull construction
///
/// *initiator*  : fn `file_handler`, fn `utils::run_daemons`
///
/// *managing*   : current file's name: &str, path in local storage to current file: &str 
///
/// *depends on* : - 
/// 
pub async fn create_watcher(filename: &str, path: &str) -> Result<Inotify, std::io::Error> {
    let src = format!("{}{}", path, filename);
    let inotify: Inotify = Inotify::init()?;
    inotify.watches().add(&src, WatchMask::ALL_EVENTS)?;
    Ok(inotify)
}

/// # Fn `create_watcher`
/// ## for managing processes by checking dep files' states  
/// 
/// *input*      : `&str`, `&[Files]`, `Arc<mpsc::Sender<u8>>`, `Arc<tokio::sync::Mutex<Vec<Inotify>>>`
///
/// *output*     : `Err` if something with dep file is wrong | `Ok(())` on successfull dep file check
///
/// *initiator*  : fn `utils::running_handler`
///
/// *managing*   : current process's name: &str, list of dep files : `&[Files]`, atomic ref counter on sender main channel for current process `Arc<mpsc::Sender<u8>>`, mut list of file watchers`Arc<tokio::sync::Mutex<Vec<Inotify>>>`
///
/// *depends on* : Files
/// 
pub async fn file_handler(
    name: &str,
    files: &[Files],
    tx: Arc<mpsc::Sender<u8>>,
    watchers: Arc<tokio::sync::Mutex<Vec<Inotify>>>,
) -> Result<(), CustomError> {
    for (i, file) in files.iter().enumerate() {
        // let src = format!("{}{}", file.src, file.filename);
        if check_file(&file.filename, &file.src).await.is_err() {
            if !is_active(name).await || is_frozen(name).await {
                return Err(CustomError::Fatal);
            }
            match file.triggers.on_delete.as_str() {
                "stay" => {
                    tx.send(9).await.unwrap();
                    continue;
                }
                "stop" => {
                    if is_active(name).await {
                        tx.send(1).await.unwrap();
                    }
                    return Err(CustomError::Fatal);
                }
                "hold" => {
                    if is_active(name).await {
                        tx.send(2).await.unwrap();
                        return Err(CustomError::Fatal);
                    }
                }
                _ => {
                    tokio::time::sleep(Duration::from_millis(50)).await;
                    tx.send(101).await.unwrap();
                    return Err(CustomError::Fatal);
                }
            }
        } else if is_active(name).await && !is_frozen(name).await {
            let watchers = watchers.clone();
            // println!("mutex: {:?}", watchers);
            let mut buffer = [0; 128];
            let mut mutex_guard = watchers.lock().await;
            if let Some(notify) = mutex_guard.get_mut(i) {
                let events = notify.read_events(&mut buffer);
                // println!("{:?}", events);
                if events.is_ok() {
                    let events: Vec<EventMask> = events
                        .unwrap()
                        .map(|mask| mask.mask)
                        .filter(|mask| {
                            *mask == EventMask::MODIFY || *mask == EventMask::DELETE_SELF
                        })
                        .collect();
                    for event in events {
                        if let EventMask::DELETE_SELF = event {
                            // ! warning (DELETE_SELF event) !
                            // println!("! warning (DELETE_SELF event) !");
                            // * watcher recreation after dealing with file recreation mechanism in text editors
                            let mutex = notify.borrow_mut();

                            // *mutex = create_watcher(&file.filename, &file.src).await.unwrap();
                            if let Ok(watcher) = create_watcher(&file.filename, &file.src).await {
                                *mutex = watcher;
                            }
                        }
                        match file.triggers.on_change.as_str() {
                            "stop" => {
                                let _ = tx.send(7).await;
                            }
                            "restart" => {
                                let _ = tx.send(8).await;
                            }
                            "stay" => {
                                let _ = tx.send(9).await;
                            }
                            _ => {
                                let _ = tx.send(101).await;
                            }
                        }
                    }
                }
            }
        }
    }
    tokio::task::yield_now().await;
    Ok(())
}

/// # Fn `check_file`
/// ## for checking existance of current file
/// 
/// *input*      : `&str`, `&str`
///
/// *output*     : `Ok(())` if file exists | `Err(_)` if not | panic on fs error
///
/// *initiator*  : fn `file_handler`
///
/// *managing*   : current file's name: `&str` and current file's path in local storage: `&str` 
///
/// *depends on* : network activity  
/// 
pub async fn check_file(filename: &str, path: &str) -> Result<(), CustomError> {
    let arc_name = Arc::new(filename.to_string());
    let arc_path = Arc::new(path.to_string());
    tokio::task::spawn_blocking(move || {
        let file_concat = format!("{}{}", arc_path, arc_name);
        let path = Path::new(&file_concat);
        if path.exists() {
            Ok(())
        } else {
            Err(CustomError::Fatal)
        }
    })
    .await
    .unwrap_or_else(|_| {
        panic!("Corrupted while file check process");
    })
}

#[cfg(test)]
mod files_unittests {
    use super::*;
    #[tokio::test]
    async fn try_to_create_watcher() {
        let res = create_watcher("dep-file", "./tests/examples/").await;
        assert!(res.is_ok());
    }
    #[tokio::test]
    async fn try_to_create_invalid_watcher() {
        let res = create_watcher("invalid-file", "/path/to/the/no/dir").await;
        assert!(res.is_err());
    }
    #[tokio::test]
    async fn check_existing_file() {
        let res = check_file("dep-file", "./tests/examples/").await;
        assert!(res.is_ok());
    }
    #[tokio::test]
    async fn check_non_existing_file() {
        let res = check_file("invalid-file", "/path/to/the/no/dir").await;
        assert!(res.is_err());
    }
}
