use crate::settings::Settings;
use common::comp::chat::ChatType;
use common_net::msg::ServerGeneral;
use server::Server;
use std::{
    ops::Add,
    sync::{
        atomic::{AtomicBool, Ordering},
        Arc,
    },
    time::{Duration, Instant},
};
use tracing::{error, info};

/// Coordinates the shutdown procedure for the server, which can be initiated by
/// either the TUI console interface or by sending the server the SIGUSR1 signal
/// which indicates the server is restarting due to an update.
pub(crate) struct ShutdownCoordinator {
    /// The instant that the last shutdown message was sent, used for
    /// calculating when to send the next shutdown message
    last_shutdown_msg: Instant,
    /// The interval that shutdown warning messages are sent at
    msg_interval: Duration,
    /// The instant that shudown was initiated at
    shutdown_initiated_at: Option<Instant>,
    /// The period to wait before shutting down after shutdown is initiated
    shutdown_grace_period: Duration,
    /// The message to use for the shutdown warning message that is sent to all
    /// connected players
    shutdown_message: String,
    /// Provided by `signal_hook` to allow observation of the SIGUSR1 signal
    sigusr1_signal: Arc<AtomicBool>,
}

impl ShutdownCoordinator {
    pub fn new(sigusr1_signal: Arc<AtomicBool>) -> Self {
        Self {
            last_shutdown_msg: Instant::now(),
            msg_interval: Duration::from_secs(30),
            shutdown_initiated_at: None,
            shutdown_grace_period: Duration::from_secs(0),
            shutdown_message: String::new(),
            sigusr1_signal,
        }
    }

    /// Initiates a graceful shutdown of the server using the specified grace
    /// period and message. When the grace period expires, the server
    /// process exits.
    pub fn initiate_shutdown(
        &mut self,
        server: &mut Server,
        grace_period: Duration,
        message: String,
    ) {
        if self.shutdown_initiated_at.is_none() {
            self.shutdown_grace_period = grace_period;
            self.shutdown_initiated_at = Some(Instant::now());
            self.shutdown_message = message;

            // Send an initial shutdown warning message to all connected clients
            self.send_shutdown_msg(server);
        } else {
            error!("Shutdown already in progress")
        }
    }

    /// Aborts an in-progress shutdown and sends a message to all connected
    /// clients.
    pub fn abort_shutdown(&mut self, server: &mut Server) {
        if self.shutdown_initiated_at.is_some() {
            self.shutdown_initiated_at = None;
            ShutdownCoordinator::send_msg(server, "The shutdown has been aborted".to_owned());
        } else {
            error!("There is no shutdown in progress");
        }
    }

    /// Called once per tick to process any pending actions related to server
    /// shutdown. If the grace period for an initiated shutdown has expired,
    /// returns `true` which triggers the loop in `main.rs` to break and
    /// exit the server process.
    pub fn check(&mut self, server: &mut Server, settings: &Settings) -> bool {
        // Check whether SIGUSR1 has been set
        self.check_sigusr1_signal(server, settings);

        // If a shutdown is in progress, check whether it's time to send another warning
        // message or shut down if the grace period has expired.
        if let Some(shutdown_initiated_at) = self.shutdown_initiated_at {
            if Instant::now() > shutdown_initiated_at.add(self.shutdown_grace_period) {
                info!("Shutting down");
                return true;
            }

            // In the last 10 seconds start sending messages every 1 second
            if let Some(time_until_shutdown) = self.time_until_shutdown() {
                if time_until_shutdown <= Duration::from_secs(10) {
                    self.msg_interval = Duration::from_secs(1);
                }
            }

            // Send another shutdown warning message to all connected clients if
            // msg_interval has expired
            if self.last_shutdown_msg + self.msg_interval <= Instant::now() {
                self.send_shutdown_msg(server);
            }
        }

        false
    }

    /// Checks whether the SIGUSR1 signal has been set, which is used to trigger
    /// a graceful shutdown for an update. [Watchtower](https://containrrr.dev/watchtower/) is configured on the main
    /// Veloren server to send SIGUSR1 instead of SIGTERM which allows us to
    /// react specifically to shutdowns that are for an update.
    /// NOTE: SIGUSR1 is not supported on Windows
    fn check_sigusr1_signal(&mut self, server: &mut Server, settings: &Settings) {
        if self.sigusr1_signal.load(Ordering::Relaxed) && self.shutdown_initiated_at.is_none() {
            info!("Received SIGUSR1 signal, initiating graceful shutdown");
            let grace_period =
                Duration::from_secs(u64::from(settings.update_shutdown_grace_period_secs));
            let shutdown_message = settings.update_shutdown_message.to_owned();
            self.initiate_shutdown(server, grace_period, shutdown_message);

            // Reset the SIGUSR1 signal indicator in case shutdown is aborted and we need to
            // trigger shutdown again
            self.sigusr1_signal.store(false, Ordering::Relaxed);
        }
    }

    /// Constructs a formatted shutdown message and sends it to all connected
    /// clients
    fn send_shutdown_msg(&mut self, server: &mut Server) {
        if let Some(time_until_shutdown) = self.time_until_shutdown() {
            let msg = format!(
                "{} in {}",
                self.shutdown_message,
                ShutdownCoordinator::duration_to_text(time_until_shutdown)
            );
            ShutdownCoordinator::send_msg(server, msg);
            self.last_shutdown_msg = Instant::now();
        }
    }

    /// Calculates the remaining time before the shutdown grace period expires
    fn time_until_shutdown(&self) -> Option<Duration> {
        let shutdown_initiated_at = self.shutdown_initiated_at?;
        let shutdown_time = shutdown_initiated_at + self.shutdown_grace_period;

        // If we're somehow trying to calculate the time until shutdown after the
        // shutdown time Instant::checked_duration_since will return None as
        // negative durations are not supported.
        shutdown_time.checked_duration_since(Instant::now())
    }

    /// Logs and sends a message to all connected clients
    fn send_msg(server: &mut Server, msg: String) {
        info!("{}", &msg);
        server.notify_players(ServerGeneral::server_msg(ChatType::CommandError, msg));
    }

    /// Converts a `Duration` into text in the format XsXm for example 1 minute
    /// 50 seconds would be converted to "1m50s", 2 minutes 0 seconds to
    /// "2m" and 0 minutes 23 seconds to "23s".
    fn duration_to_text(duration: Duration) -> String {
        let secs = duration.as_secs_f32().round() as i32 % 60;
        let mins = duration.as_secs_f32().round() as i32 / 60;

        let mut text = String::new();
        if mins > 0 {
            text.push_str(format!("{}m", mins).as_str())
        }
        if secs > 0 {
            text.push_str(format!("{}s", secs).as_str())
        }
        text
    }
}
