#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
#![allow(clippy::needless_return)]

mod app;
mod client;
mod config;
mod dirs;
mod icon;
mod lock;
mod models;
mod oauth;
mod pipe;
mod simconnect;
mod state_machine;

use crate::config::Config;
use clap::Parser;
use lock::Lock;
use oauth::{start_code_listener, start_code_to_token};
use pipe::Pipe;
use state_machine::Event;
use tokio::{sync::broadcast::channel, task::JoinSet};
use tracing::Level;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};

pub static AVAM_APP_ID: &str = "AvamToast-ECEB71694A5E6105";
pub static BASE_URL: &str = "https://avam.avii.nl";
pub static PROJECT_NAME: &str = "Avii's Virtual Airline Manager";
pub static COPYRIGHT: &str = "Avii's Virtual Airline Manager © 2024";
pub static CLIENT_ID: uuid::Uuid = uuid::uuid!("f9525060-0a34-4233-87e2-0f9990b7c6db");
pub static REDIRECT_URI: &str = "avam:token";

#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct Arguments {
    #[arg(short, long)]
    code: Option<String>,
    #[arg(short, long, action)]
    force: bool,
}

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
    dotenvy::dotenv().ok();

    init_logging()?;

    let (event_sender, event_receiver) = channel(10);
    let args = Arguments::parse();

    if handle_single_instance(&args).await? {
        return Ok(());
    }

    register_url_scheme()?;
    register_notification_handler()?;

    let config = Config::new()?;

    let mut futures = JoinSet::new();

    // Register Quit handler
    let sender = event_sender.clone();
    let mut ctrl_c_counter = 0;
    ctrlc::set_handler(move || {
        tracing::info!("CTRL_C: Quit singal sent");
        ctrl_c_counter += 1;
        if ctrl_c_counter >= 3 {
            let _ = unregister_url_scheme();
            Lock::unlock();
            std::process::exit(1);
        }

        if let Err(e) = sender.send(Event::Quit) {
            tracing::error!("{:#?}", e)
        };
    })?;

    // Start the State Machine
    let c = config.clone();
    let sender = event_sender.clone();
    let receiver = event_receiver.resubscribe();
    futures.spawn(state_machine::start(c, sender, receiver));

    // // Start the code listener
    let receiver = event_receiver.resubscribe();
    let (pipe_sender, pipe_receiver) = tokio::sync::broadcast::channel(10);
    futures.spawn(start_code_listener(pipe_sender, receiver));

    // Start token listener
    let c = config.clone();
    let sender = event_sender.clone();
    let receiver = event_receiver.resubscribe();
    futures.spawn(start_code_to_token(c, pipe_receiver, sender, receiver));

    // Prepare channels for socket <-> simconnect
    let (simconnect_sender, simconnect_receiver) = channel(10);
    let (socket_sender, socket_receiver) = channel(10);

    // Start the websocket client
    let c = config.clone();
    let sender = event_sender.clone();
    let receiver = event_receiver.resubscribe();
    futures.spawn(client::start(
        c,
        simconnect_sender,
        socket_receiver,
        sender,
        receiver,
    ));

    // Start the simconnect listener
    // The simconnect sends data to the webscoket
    // It also receives data from the websocket to do things like set plane id and fuel and such things
    // If possible even position
    let sender = event_sender.clone();
    let receiver = event_receiver.resubscribe();
    futures.spawn(simconnect::start(
        simconnect_receiver,
        socket_sender,
        sender,
        receiver,
    ));

    // Start the Tray Icon
    let c = config.clone();
    let sender = event_sender.clone();
    let receiver = event_receiver.resubscribe();
    app::start(c, sender, receiver).await?;

    // Wait for everything to finish
    while let Some(result) = futures.join_next().await {
        if let Ok(Err(e)) = result {
            panic!("{:#?}", e);
        }
    }

    // Cleanup
    unregister_url_scheme()?;
    unregister_notification_handler()?;
    Lock::unlock();

    Ok(())
}

//
//
//
//
//
//
//
//
//
//
//
//

/// returns `Ok(true)` if we need to quit gracefully
/// returns `Err(e)` if we're already running
async fn handle_single_instance(args: &Arguments) -> Result<bool, anyhow::Error> {
    let lock = Lock::new(args.force)?;

    if lock.is_locked() && args.code.is_none() {
        return Err(anyhow::anyhow!("Lockfile exists, exiting."));
    }

    if let Some(code) = &args.code {
        Pipe::send(code).await?;
        return Ok(true);
    }

    Lock::lock();
    Ok(false)
}

fn register_url_scheme() -> Result<(), anyhow::Error> {
    use winreg::enums::*;
    use winreg::RegKey;

    let hkcu = RegKey::predef(HKEY_CURRENT_USER);
    let avam_schema_root = hkcu.create_subkey("Software\\Classes\\avam")?;
    avam_schema_root.0.set_value("URL Protocol", &"")?;
    let command = avam_schema_root.0.create_subkey("shell\\open\\command")?;

    let current_exec = std::env::current_exe()?;

    command.0.set_value(
        "",
        &format!("\"{}\" -c \"%1\"", current_exec.to_str().unwrap()),
    )?;

    Ok(())
}

fn unregister_url_scheme() -> Result<(), anyhow::Error> {
    use winreg::enums::*;
    use winreg::RegKey;

    let hkcu = RegKey::predef(HKEY_CURRENT_USER);
    hkcu.delete_subkey_all("Software\\Classes\\avam").ok();

    Ok(())
}

fn register_notification_handler() -> Result<(), anyhow::Error> {
    use winreg::enums::*;
    use winreg::RegKey;

    let hkcu = RegKey::predef(HKEY_CURRENT_USER);
    let avam_schema_root = hkcu.create_subkey(format!(
        "Software\\Classes\\AppUserModelId\\{}",
        AVAM_APP_ID
    ))?;
    let current_exec = std::env::current_exe()?;
    let mut icon = current_exec.parent().unwrap().to_path_buf();
    icon.push("icon.png");

    avam_schema_root
        .0
        .set_value("DisplayName", &crate::PROJECT_NAME)?;
    avam_schema_root.0.set_value("IconBackgroundColor", &"0")?;
    avam_schema_root
        .0
        .set_value("IconUri", &icon.to_str().unwrap())?;

    Ok(())
}

fn unregister_notification_handler() -> Result<(), anyhow::Error> {
    use winreg::enums::*;
    use winreg::RegKey;

    let hkcu = RegKey::predef(HKEY_CURRENT_USER);
    hkcu.delete_subkey_all(format!(
        "Software\\Classes\\AppUserModelId\\{}",
        AVAM_APP_ID
    ))
    .ok();

    Ok(())
}

fn init_logging() -> Result<(), anyhow::Error> {
    #[cfg(not(debug_assertions))]
    use dirs::Dirs;

    #[cfg(not(debug_assertions))]
    use std::{
        fs::{self, File},
        sync::Arc,
    };

    #[cfg(not(debug_assertions))]
    let log_file = Dirs::get_log_file()?;

    #[cfg(not(debug_assertions))]
    if !log_file.exists() {
        fs::write(&log_file, "")?;
    }

    #[cfg(not(debug_assertions))]
    let file = File::options().append(true).open(&log_file)?;

    let fmt = tracing_subscriber::fmt::layer();

    #[cfg(not(debug_assertions))]
    let fmt = fmt.with_ansi(false).with_writer(Arc::new(file));

    let fmt = fmt.with_filter(tracing_subscriber::filter::filter_fn(|metadata| {
        metadata.level() < &Level::TRACE
    }));

    tracing_subscriber::registry().with(fmt).init();

    Ok(())
}
