use std::time::Duration;
use thiserror::Error;

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

use crate::{
    config::{Config, ConfigError},
    models::*,
    pipe::Pipe,
    state_machine::Event,
    BASE_URL, CLIENT_ID, REDIRECT_URI,
};

#[derive(Debug, Error)]
pub enum OpenBrowserError {
    #[error(transparent)]
    SerdeQs(#[from] serde_qs::Error),
    #[error(transparent)]
    Io(#[from] std::io::Error),
    #[error(transparent)]
    Config(#[from] ConfigError),
}

pub fn open_browser(config: Config) -> Result<(), OpenBrowserError> {
    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()))?;

    let code_challenge = match code_challenge_method {
        CodeChallengeMethod::Plain => {
            use base64::prelude::*;
            BASE64_URL_SAFE_NO_PAD.encode(code_verifier.to_string())
        }
        CodeChallengeMethod::Sha256 => {
            use base64::prelude::*;
            BASE64_URL_SAFE_NO_PAD.encode(sha256::digest(code_verifier.to_string()))
        }
    };

    let request = AuthorizeRequest::new(
        CLIENT_ID,
        ResponseType::Code,
        None,
        code_challenge.clone(),
        Some(code_challenge_method.clone()),
        RedirectUri::new(REDIRECT_URI),
        None,
    );

    let qs = serde_qs::to_string(&request)?;

    open::that(format!("{}/oauth2/authorize?{}", BASE_URL, qs))?;

    Ok(())
}

pub async fn start_code_listener(
    pipe_sender: Sender<AuthorizationResponse>,
    event_receiver: Receiver<Event>,
) -> Result<(), anyhow::Error> {
    let pipe = Pipe::new(event_receiver.resubscribe());
    pipe.listen(pipe_sender).await?;
    Ok(())
}

pub async fn start_code_to_token(
    config: Config,
    mut pipe_receiver: Receiver<AuthorizationResponse>,
    event_sender: Sender<Event>,
    mut event_receiver: Receiver<Event>,
) -> Result<(), anyhow::Error> {
    loop {
        tokio::select! {
            _ = sleep(Duration::from_millis(100)) => {
                if let Ok(Event::Quit) = event_receiver.try_recv() {
                    tracing::info!("Shutting down Code Transformer");
                    break;
                }
            }
            Ok(response) = pipe_receiver.recv() => {
                let r = AuthorizationCodeRequest::new(
                    GrantType::AuthorizationCode,
                    response.code(),
                    REDIRECT_URI.into(),
                    CLIENT_ID,
                    config.code_verifier().unwrap()
                );

                let qs = serde_qs::to_string(&r)?;

                let client = reqwest::Client::new();

                let response = client
                    .post(format!("{}/oauth2/token", BASE_URL))
                    .body(qs)
                    .send()
                    .await?;

                let response: AuthorizationCodeResponse = response.json().await?;
                let token = response.token();

                config.set_token(Some(token.clone()))?;

                event_sender.send(Event::TokenReceived)?;
            }
        }
    }

    tracing::info!("Code Transformer Shutdown");
    Ok(())
}
