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

use crate::{
    config::Config,
    models::{CodeChallengeMethod, CodeVerifier},
    oauth,
};

#[derive(Debug, PartialEq)]
pub enum State {
    Init,
    AppStart {
        config: Config,
    },
    Authenticate {
        code_verifier: CodeVerifier,
        code_challenge_method: CodeChallengeMethod,
    },
    Connect {
        token: String,
    },
    WaitForSim,
    Running,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Event {
    Ready {
        config: Config,
    },
    StartAuthenticate {
        code_verifier: CodeVerifier,
        code_challenge_method: CodeChallengeMethod,
    }, // should not be string
    TokenReceived {
        token: String,
    }, // 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
    Quit,
}

impl State {
    pub async fn next(self, event: Event) -> State {
        match (self, event) {
            // (Current State, SomeEvent) => NextState
            (State::Init, Event::Ready { config }) => State::AppStart { config },
            (
                State::AppStart { .. },
                Event::StartAuthenticate {
                    code_verifier: code_challenge,
                    code_challenge_method,
                },
            ) => Self::Authenticate {
                code_verifier: code_challenge,
                code_challenge_method,
            }, // Goto Authenticate

            (State::AppStart { .. }, Event::TokenReceived { token }) => State::Connect { token },
            (State::Authenticate { .. }, Event::TokenReceived { token }) => {
                State::Connect { token }
            }

            (State::Connect { .. }, Event::Connected) => todo!(), // Goto WaitForSim

            (State::WaitForSim, Event::SimConnected) => todo!(), // Goto Running

            (State::Running, Event::Disconnected) => todo!(), // Goto Connect
            (State::Running, Event::SimDisconnected) => todo!(), // Goto WaitForSim

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

            _ => panic!("Invalid state transition"),
        }
    }

    pub async fn run(&self, signal: Sender<Event>) -> Result<(), anyhow::Error> {
        match self {
            State::Init => Ok(()),
            State::AppStart { config } => {
                if let Some(token) = config.token() {
                    signal.send(Event::TokenReceived {
                        token: token.to_string(),
                    })?;
                } else {
                    let code_verifier = CodeVerifier::new();
                    let code_challenge_method = CodeChallengeMethod::Sha256;

                    config.set_code_verifier(Some(code_verifier.clone()))?;
                    config.set_code_challenge_method(Some(code_challenge_method.clone()))?;

                    signal.send(Event::StartAuthenticate {
                        code_verifier,
                        code_challenge_method,
                    })?;
                }
                Ok(())
            }
            State::Authenticate {
                code_verifier,
                code_challenge_method,
            } => {
                oauth::open_browser(code_verifier.clone(), code_challenge_method.clone())?;

                Ok(())
            }
            State::Connect { token } => {
                println!("Holyshit we've got a token: {}", token);
                Ok(())
            }
            State::WaitForSim => Ok(()),
            State::Running => 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()).await?;

    loop {
        if let Ok(event) = event_receiver.recv().await {
            if event == Event::Quit {
                println!("Shutting down State Machine");
                break;
            }

            state = state.next(event).await;

            // before run
            if let State::Connect { token } = &state {
                // before run Connect, save the given token in config
                config.set_token(Some(token.clone()))?;
            }

            state.run(event_sender.clone()).await?;
        }
    }

    println!("State Machine Shutdown");
    Ok(())
}
