use std::sync::{
    Arc,
    atomic::{AtomicBool, Ordering},
};

use alloy_network::Network;
use alloy_primitives::ChainId;
use tokio::sync::{Mutex, RwLock};
use uuid::Uuid;

use crate::wallet_browser::{
    queue::RequestQueue,
    types::{
        BrowserChainSwitchRequest, BrowserChainSwitchResponse, BrowserSignRequest,
        BrowserSignResponse, BrowserTransactionRequest, BrowserTransactionResponse, Connection,
    },
};

#[cfg(feature = "tempo")]
use crate::wallet_browser::types::{
    BrowserKeyAuthorizationRequest, BrowserKeyAuthorizationResponse,
};

#[derive(Debug, Clone)]
pub(crate) struct BrowserWalletState<N: Network> {
    /// Current information about the wallet connection.
    connection: Arc<RwLock<Option<Connection>>>,
    /// Request/response queue for transactions.
    transactions:
        Arc<Mutex<RequestQueue<BrowserTransactionRequest<N>, BrowserTransactionResponse>>>,
    /// Request/response queue for signings.
    signings: Arc<Mutex<RequestQueue<BrowserSignRequest, BrowserSignResponse>>>,
    /// Request/response queue for chain switches.
    chain_switches: Arc<Mutex<RequestQueue<BrowserChainSwitchRequest, BrowserChainSwitchResponse>>>,
    /// Request/response queue for Tempo `KeyAuthorization` signings.
    #[cfg(feature = "tempo")]
    key_authorizations:
        Arc<Mutex<RequestQueue<BrowserKeyAuthorizationRequest, BrowserKeyAuthorizationResponse>>>,
    /// Unique session token for the wallet browser instance.
    /// The CSP on the served page prevents this token from being loaded by other origins.
    session_token: String,
    /// If true, the server is running in development mode.
    /// This relaxes certain security restrictions for local development.
    ///
    /// **WARNING**: This should only be used in a development environment.
    development: bool,
    /// Whether the server is shutting down. Once flipped, the `/api/session`
    /// endpoint reports `alive: false` so the webapp can stop polling.
    shutting_down: Arc<AtomicBool>,
}

impl<N: Network> BrowserWalletState<N> {
    /// Create a new browser wallet state.
    pub fn new(session_token: String, development: bool) -> Self {
        Self {
            connection: Arc::new(RwLock::new(None)),
            transactions: Arc::new(Mutex::new(RequestQueue::new())),
            signings: Arc::new(Mutex::new(RequestQueue::new())),
            chain_switches: Arc::new(Mutex::new(RequestQueue::new())),
            #[cfg(feature = "tempo")]
            key_authorizations: Arc::new(Mutex::new(RequestQueue::new())),
            session_token,
            development,
            shutting_down: Arc::new(AtomicBool::new(false)),
        }
    }

    /// Get the session token.
    pub fn session_token(&self) -> &str {
        &self.session_token
    }

    /// Check if in development mode.
    /// This relaxes certain security restrictions for local development.
    ///
    /// **WARNING**: This should only be used in a development environment.
    pub const fn is_development(&self) -> bool {
        self.development
    }

    /// Mark the server as shutting down.
    pub fn set_shutting_down(&self) {
        self.shutting_down.store(true, Ordering::SeqCst);
    }

    /// Whether the server is shutting down.
    pub fn is_shutting_down(&self) -> bool {
        self.shutting_down.load(Ordering::SeqCst)
    }

    /// Check if wallet is connected.
    pub async fn is_connected(&self) -> bool {
        self.connection.read().await.is_some()
    }

    /// Get current connection information.
    pub async fn get_connection(&self) -> Option<Connection> {
        *self.connection.read().await
    }

    /// Set connection information. When the new connection is `None`, all
    /// pending wallet requests are failed with a synthetic `Wallet disconnected`
    /// error response so that in-flight callers return immediately rather than
    /// waiting for their per-request timeout.
    pub async fn set_connection(&self, connection: Option<Connection>) {
        *self.connection.write().await = connection;

        if connection.is_none() {
            self.fail_pending_with_disconnect().await;
        }
    }

    /// Fail all in-flight wallet requests with a synthetic `Wallet disconnected`
    /// error response.
    async fn fail_pending_with_disconnect(&self) {
        {
            let mut txs = self.transactions.lock().await;
            for id in txs.drain_request_ids() {
                txs.add_response(
                    id,
                    BrowserTransactionResponse {
                        id,
                        hash: None,
                        error: Some("Wallet disconnected".to_string()),
                    },
                );
            }
        }
        {
            let mut signs = self.signings.lock().await;
            for id in signs.drain_request_ids() {
                signs.add_response(
                    id,
                    BrowserSignResponse {
                        id,
                        signature: None,
                        error: Some("Wallet disconnected".to_string()),
                    },
                );
            }
        }
        {
            let mut chain_switches = self.chain_switches.lock().await;
            for id in chain_switches.drain_request_ids() {
                chain_switches.add_response(
                    id,
                    BrowserChainSwitchResponse {
                        id,
                        chain_id: None,
                        error: Some("Wallet disconnected".to_string()),
                    },
                );
            }
        }
        #[cfg(feature = "tempo")]
        {
            let mut key_authorizations = self.key_authorizations.lock().await;
            for id in key_authorizations.drain_request_ids() {
                key_authorizations.add_response(
                    id,
                    BrowserKeyAuthorizationResponse {
                        id,
                        signed_hex: None,
                        error: Some("Wallet disconnected".to_string()),
                    },
                );
            }
        }
    }

    /// Add a transaction request.
    pub async fn add_transaction_request(&self, request: BrowserTransactionRequest<N>) {
        self.transactions.lock().await.add_request(request);
    }

    /// Check if a transaction request exists.
    pub async fn has_transaction_request(&self, id: &Uuid) -> bool {
        self.transactions.lock().await.has_request(id)
    }

    /// Read the next transaction request.
    pub async fn read_next_transaction_request(&self) -> Option<BrowserTransactionRequest<N>> {
        self.transactions.lock().await.read_request().cloned()
    }

    // Remove a transaction request.
    pub async fn remove_transaction_request(&self, id: &Uuid) {
        self.transactions.lock().await.remove_request(id);
    }

    /// Add transaction response.
    pub async fn add_transaction_response(&self, response: BrowserTransactionResponse) {
        let id = response.id;
        let mut transactions = self.transactions.lock().await;
        transactions.add_response(id, response);
        transactions.remove_request(&id);
    }

    /// Get transaction response, removing it from the queue.
    pub async fn get_transaction_response(&self, id: &Uuid) -> Option<BrowserTransactionResponse> {
        self.transactions.lock().await.get_response(id)
    }

    /// Add a signing request.
    pub async fn add_signing_request(&self, request: BrowserSignRequest) {
        self.signings.lock().await.add_request(request);
    }

    /// Check if a signing request exists.
    pub async fn has_signing_request(&self, id: &Uuid) -> bool {
        self.signings.lock().await.has_request(id)
    }

    /// Read the next signing request.
    pub async fn read_next_signing_request(&self) -> Option<BrowserSignRequest> {
        self.signings.lock().await.read_request().cloned()
    }

    /// Remove a signing request.
    pub async fn remove_signing_request(&self, id: &Uuid) {
        self.signings.lock().await.remove_request(id);
    }

    /// Add signing response.
    pub async fn add_signing_response(&self, response: BrowserSignResponse) {
        let id = response.id;
        let mut signings = self.signings.lock().await;
        signings.add_response(id, response);
        signings.remove_request(&id);
    }

    /// Get signing response, removing it from the queue.
    pub async fn get_signing_response(&self, id: &Uuid) -> Option<BrowserSignResponse> {
        self.signings.lock().await.get_response(id)
    }

    /// Add a chain-switch request.
    pub async fn add_chain_switch_request(&self, request: BrowserChainSwitchRequest) {
        self.chain_switches.lock().await.add_request(request);
    }

    /// Check if a chain-switch request exists.
    pub async fn has_chain_switch_request(&self, id: &Uuid) -> bool {
        self.chain_switches.lock().await.has_request(id)
    }

    /// Get the target chain ID for a pending chain-switch request.
    pub async fn chain_switch_request_chain_id(&self, id: &Uuid) -> Option<ChainId> {
        self.chain_switches.lock().await.get_request(id).map(|request| request.chain_id)
    }

    /// Read the next chain-switch request.
    pub async fn read_next_chain_switch_request(&self) -> Option<BrowserChainSwitchRequest> {
        self.chain_switches.lock().await.read_request().cloned()
    }

    /// Remove a chain-switch request.
    pub async fn remove_chain_switch_request(&self, id: &Uuid) {
        self.chain_switches.lock().await.remove_request(id);
    }

    /// Add chain-switch response.
    pub async fn add_chain_switch_response(&self, response: BrowserChainSwitchResponse) {
        let id = response.id;
        if let Some(chain_id) = response.chain_id {
            let mut connection = self.connection.write().await;
            if let Some(current) = *connection {
                *connection = Some(Connection { chain_id, ..current });
            }
        }

        let mut chain_switches = self.chain_switches.lock().await;
        chain_switches.add_response(id, response);
        chain_switches.remove_request(&id);
    }

    /// Get chain-switch response, removing it from the queue.
    pub async fn get_chain_switch_response(&self, id: &Uuid) -> Option<BrowserChainSwitchResponse> {
        self.chain_switches.lock().await.get_response(id)
    }

    // -- Tempo `KeyAuthorization` signings -----------------------------------

    /// Add a Tempo `KeyAuthorization` signing request.
    #[cfg(feature = "tempo")]
    pub async fn add_key_authorization_request(&self, request: BrowserKeyAuthorizationRequest) {
        self.key_authorizations.lock().await.add_request(request);
    }

    /// Check if a Tempo `KeyAuthorization` signing request exists.
    #[cfg(feature = "tempo")]
    pub async fn has_key_authorization_request(&self, id: &Uuid) -> bool {
        self.key_authorizations.lock().await.has_request(id)
    }

    /// Read the next Tempo `KeyAuthorization` signing request.
    #[cfg(feature = "tempo")]
    pub async fn read_next_key_authorization_request(
        &self,
    ) -> Option<BrowserKeyAuthorizationRequest> {
        self.key_authorizations.lock().await.read_request().cloned()
    }

    /// Remove a Tempo `KeyAuthorization` signing request.
    #[cfg(feature = "tempo")]
    pub async fn remove_key_authorization_request(&self, id: &Uuid) {
        self.key_authorizations.lock().await.remove_request(id);
    }

    /// Add a Tempo `KeyAuthorization` signing response.
    #[cfg(feature = "tempo")]
    pub async fn add_key_authorization_response(&self, response: BrowserKeyAuthorizationResponse) {
        let id = response.id;
        let mut key_authorizations = self.key_authorizations.lock().await;
        key_authorizations.add_response(id, response);
        key_authorizations.remove_request(&id);
    }

    /// Get a Tempo `KeyAuthorization` signing response, removing it from the queue.
    #[cfg(feature = "tempo")]
    pub async fn get_key_authorization_response(
        &self,
        id: &Uuid,
    ) -> Option<BrowserKeyAuthorizationResponse> {
        self.key_authorizations.lock().await.get_response(id)
    }
}
