use std::sync::{Arc, Mutex, RwLock, atomic::AtomicBool};

use axum::extract::FromRef;
use axum_extra::extract::cookie::Key;
use defguard_common::{db::models::Settings, types::proxy::ProxyControlMessage};
use reqwest::Client;
use serde_json::json;
use sqlx::PgPool;
use tokio::{
    sync::{
        broadcast::Sender,
        mpsc::{UnboundedReceiver, UnboundedSender},
    },
    task::spawn,
};

use crate::{
    auth::failed_login::FailedLoginMap,
    db::{AppEvent, WebHook},
    error::WebError,
    events::ApiEvent,
    grpc::{GatewayEvent, send_multiple_wireguard_events, send_wireguard_event},
    version::IncompatibleComponents,
};

const X_DEFGUARD_EVENT: &str = "x-defguard-event";

#[derive(Clone)]
pub struct AppState {
    pub pool: PgPool,
    tx: UnboundedSender<AppEvent>,
    pub wireguard_tx: Sender<GatewayEvent>,
    pub web_reload_tx: tokio::sync::broadcast::Sender<()>,
    pub failed_logins: Arc<Mutex<FailedLoginMap>>,
    key: Key,
    pub event_tx: UnboundedSender<ApiEvent>,
    pub incompatible_components: Arc<RwLock<IncompatibleComponents>>,
    pub proxy_control_tx: tokio::sync::mpsc::Sender<ProxyControlMessage>,
    /// Reflects whether the HTTP server is currently running with TLS
    pub tls_active: Arc<AtomicBool>,
}

impl AppState {
    pub(crate) fn trigger_action(&self, event: AppEvent) {
        let event_name = event.name().to_owned();
        match self.tx.send(event) {
            Ok(()) => info!("Sent trigger {event_name}"),
            Err(err) => error!("Error sending trigger {event_name}: {err}"),
        }
    }

    /// Handle webhook events
    async fn handle_triggers(pool: PgPool, mut rx: UnboundedReceiver<AppEvent>) {
        let reqwest_client = Client::builder().user_agent("reqwest").build().unwrap();
        while let Some(msg) = rx.recv().await {
            debug!("WebHook triggered");
            debug!("Retrieving webhooks");
            if let Ok(webhooks) = WebHook::all_enabled(&pool, &msg).await {
                debug!("Found webhooks: {webhooks:?}");
                let (payload, event) = match msg {
                    AppEvent::UserCreated(user) => (json!(user), "user_created"),
                    AppEvent::UserModified(user) => (json!(user), "user_modified"),
                    AppEvent::UserDeleted(username) => {
                        (json!({"username": username}), "user_deleted")
                    }
                    AppEvent::HWKeyProvision(data) => (json!(data), "user_keys"),
                };
                for webhook in webhooks {
                    match reqwest_client
                        .post(&webhook.url)
                        .bearer_auth(&webhook.token)
                        .header(X_DEFGUARD_EVENT, event)
                        .json(&payload)
                        .send()
                        .await
                    {
                        Ok(res) => {
                            info!("Trigger sent to {}, status {}", webhook.url, res.status());
                        }
                        Err(err) => {
                            error!("Error sending trigger to {}: {err}", webhook.url);
                        }
                    }
                }
            }
        }
    }

    /// Sends given `GatewayEvent` to be handled by gateway GRPC server.
    /// Convenience wrapper around [`send_wireguard_event`]
    pub fn send_wireguard_event(&self, event: GatewayEvent) {
        send_wireguard_event(event, &self.wireguard_tx);
    }

    /// Sends multiple events to be handled by gateway GRPC server.
    /// Convenience wrapper around [`send_multiple_wireguard_events`]
    pub fn send_multiple_wireguard_events(&self, events: Vec<GatewayEvent>) {
        send_multiple_wireguard_events(events, &self.wireguard_tx);
    }

    /// Sends event to the main event router
    ///
    /// This method is fallible since events are used for communication between services
    pub fn emit_event(&self, event: ApiEvent) -> Result<(), WebError> {
        Ok(self.event_tx.send(event)?)
    }

    pub fn webauthn(&self) -> Result<Arc<webauthn_rs::Webauthn>, WebError> {
        let settings = Settings::get_current_settings();
        settings.build_webauthn().map(Arc::new).map_err(|err| {
            error!("Failed to build WebAuthn configuration from current settings: {err}");
            WebError::Http(axum::http::StatusCode::INTERNAL_SERVER_ERROR)
        })
    }

    /// Create application state
    pub fn new(
        pool: PgPool,
        tx: UnboundedSender<AppEvent>,
        rx: UnboundedReceiver<AppEvent>,
        wireguard_tx: Sender<GatewayEvent>,
        web_reload_tx: tokio::sync::broadcast::Sender<()>,
        key: Key,
        failed_logins: Arc<Mutex<FailedLoginMap>>,
        event_tx: UnboundedSender<ApiEvent>,
        incompatible_components: Arc<RwLock<IncompatibleComponents>>,
        proxy_control_tx: tokio::sync::mpsc::Sender<ProxyControlMessage>,
        tls_active: Arc<AtomicBool>,
    ) -> Self {
        spawn(Self::handle_triggers(pool.clone(), rx));

        Self {
            pool,
            tx,
            wireguard_tx,
            web_reload_tx,
            failed_logins,
            key,
            event_tx,
            incompatible_components,
            proxy_control_tx,
            tls_active,
        }
    }
}

impl FromRef<AppState> for Key {
    fn from_ref(state: &AppState) -> Self {
        state.key.clone()
    }
}
