use std::time::Duration;

use tokio::{
    sync::broadcast::{Receiver, Sender},
    time::sleep,
};

use crate::{config::Config, oauth};

#[derive(Debug, Clone, PartialEq)]
pub enum State {
    Init,
    AppStart,
    Shutdown,
    Authenticate,
    Connect,
    WaitForSim,
    InSim,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Event {
    Ready,
    StartAuthenticate, // should not be string
    TokenReceived, // AppStart and Authenticate can fire off TokenReceived to transition into Connect
    Connected, // Once connected to the socket, and properly authenticated, fire off Connected to transition to WaitForSim
    Disconnected, // If for whatever reason we're disconnected from the backend, we need to transition back to Connect
    SimConnected, // SimConnect is connected, we're in the world and ready to send data, transition to Running
    SimDisconnected, // SimConnect is disconnected, we've finished the flight and exited back to the menu, transition back to WaitForSim
    Logout,
    Quit,
}

impl State {
    pub async fn next(self, event: Event) -> State {
        match (self.clone(), event.clone()) {
            // (Current State, SomeEvent) => NextState
            (_, Event::Ready) => State::AppStart,
            (_, Event::Logout) => State::AppStart,
            (_, Event::StartAuthenticate) => Self::Authenticate, // Goto Authenticate

            (_, Event::TokenReceived) => State::Connect,

            (_, Event::Connected) => State::WaitForSim, // Goto WaitForSim

            (_, Event::Disconnected) => {
                sleep(Duration::from_secs(5)).await; // wait 5 seconds before reconnecting
                tracing::info!("Attempting reconnect");
                State::AppStart // Goto Connect
            }

            (_, Event::SimConnected) => State::InSim, // Goto InSim
            (_, Event::SimDisconnected) => State::WaitForSim, // Goto WaitForSim

            (_, Event::Quit) => State::Shutdown, // All events can go into quit, to shutdown the application
        }
    }

    pub async fn run(&self, signal: Sender<Event>, config: Config) -> Result<(), anyhow::Error> {
        match self {
            State::Init => Ok(()),
            State::AppStart => {
                if config.token().is_some() {
                    signal.send(Event::TokenReceived)?;
                } else {
                    signal.send(Event::StartAuthenticate)?;
                }
                Ok(())
            }
            State::Authenticate => {
                if config.open_browser() {
                    oauth::open_browser(config.clone())?;
                }

                Ok(())
            }
            State::Connect => Ok(()),
            State::WaitForSim => Ok(()),
            State::InSim => Ok(()),
            State::Shutdown => Ok(()),
        }
    }
}

pub async fn start(
    config: Config,
    event_sender: Sender<Event>,
    mut event_receiver: Receiver<Event>,
) -> Result<(), anyhow::Error> {
    let mut state = State::Init;

    state.run(event_sender.clone(), config.clone()).await?;

    loop {
        if let Ok(event) = event_receiver.try_recv() {
            state = state.next(event.clone()).await;

            state.run(event_sender.clone(), config.clone()).await?;

            if event == Event::Quit {
                tracing::info!("Shutting down State Machine");
                break;
            }
        }
        sleep(Duration::from_millis(100)).await;
    }

    tracing::info!("State Machine Shutdown");
    Ok(())
}
