//! Token-related API operations.

use alloy_primitives::Address;
use om_primitives_types::transaction::payload::{
    TokenAuthorityPayload, TokenBlacklistPayload, TokenBurnPayload, TokenMetadataUpdatePayload, TokenMintPayload,
    TokenPausePayload, TokenWhitelistPayload,
};
use om_rest_types::{
    requests::{
        TokenAuthorityRequest, TokenBlacklistRequest, TokenBurnRequest, TokenMetadataUpdateRequest, TokenMintRequest,
        TokenPauseRequest, TokenWhitelistRequest,
    },
    responses::{MintInfo, TransactionResponse},
};

use crate::{
    client::{
        Client,
        config::{
            api_path,
            endpoints::tokens::{
                BURN, GRANT_AUTHORITY, MANAGE_BLACKLIST, MANAGE_WHITELIST, MINT, PAUSE, TOKEN_METADATA, UPDATE_METADATA,
            },
        },
    },
    crypto::sign_transaction_payload,
    error::Result,
};

impl Client {
    /// Mint tokens to an account.
    ///
    /// # Arguments
    ///
    /// * `payload` - Token mint parameters
    /// * `private_key` - Private key for signing the transaction (must have
    ///   mint authority)
    ///
    /// # Returns
    ///
    /// The transaction result.
    pub async fn mint_token(&self, data: TokenMintPayload, private_key: &str) -> Result<TransactionResponse> {
        let signature = sign_transaction_payload(&data, private_key)?;
        let request = TokenMintRequest { data, signature };

        self.post(&api_path(MINT), &request).await
    }

    /// Burn tokens from an account.
    ///
    /// # Arguments
    ///
    /// * `payload` - Token burn parameters
    /// * `private_key` - Private key for signing the transaction (must have
    ///   burn authority)
    ///
    /// # Returns
    ///
    /// The transaction result.
    pub async fn burn_token(&self, data: TokenBurnPayload, private_key: &str) -> Result<TransactionResponse> {
        let signature = sign_transaction_payload(&data, private_key)?;
        let request = TokenBurnRequest { data, signature };

        self.post(&api_path(BURN), &request).await
    }

    /// Grant authority for a token to an address.
    ///
    /// # Arguments
    ///
    /// * `payload` - Authority grant parameters
    /// * `private_key` - Private key for signing the transaction (must have
    ///   master authority)
    ///
    /// # Returns
    ///
    /// The transaction result.
    pub async fn grant_authority(&self, data: TokenAuthorityPayload, private_key: &str) -> Result<TransactionResponse> {
        let signature = sign_transaction_payload(&data, private_key)?;
        let request = TokenAuthorityRequest { data, signature };

        self.post(&api_path(GRANT_AUTHORITY), &request).await
    }

    /// Revoke authority for a token from an address.
    ///
    /// Note: This method uses the same `/v1/tokens/grant_authority` endpoint as
    /// grant_authority(), but with `AuthorityAction::Revoke` in the payload
    /// to indicate a revoke operation.
    ///
    /// # Arguments
    ///
    /// * `payload` - Authority revoke parameters (with action set to
    ///   AuthorityAction::Revoke)
    /// * `private_key` - Private key for signing the transaction (must have
    ///   master authority)
    ///
    /// # Returns
    ///
    /// The transaction result.
    pub async fn revoke_authority(
        &self,
        data: TokenAuthorityPayload,
        private_key: &str,
    ) -> Result<TransactionResponse> {
        let signature = sign_transaction_payload(&data, private_key)?;
        let request = TokenAuthorityRequest { data, signature };

        self.post(&api_path(GRANT_AUTHORITY), &request).await
    }

    /// Get token metadata by mint address.
    ///
    /// # Arguments
    ///
    /// * `mint_address` - The token mint address
    ///
    /// # Returns
    ///
    /// The token metadata.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use std::str::FromStr;
    ///
    /// use alloy_primitives::Address;
    /// use onemoney_protocol::Client;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = Client::mainnet()?;
    ///     let mint = Address::from_str("0x1234567890abcdef1234567890abcdef12345678")?;
    ///
    ///     let mint_info = client.get_token_metadata(mint).await?;
    ///     println!("Token: {}", mint_info.symbol);
    ///
    ///     Ok(())
    /// }
    /// ```
    pub async fn get_token_metadata(&self, mint_address: Address) -> Result<MintInfo> {
        let path = api_path(&format!("{}?token={}", TOKEN_METADATA, mint_address));
        let response: MintInfo = self.get(&path).await?;
        Ok(response)
    }

    /// Pause or unpause a token.
    ///
    /// # Arguments
    ///
    /// * `payload` - Token pause parameters
    /// * `private_key` - Private key for signing the transaction (must have
    ///   pause authority)
    ///
    /// # Returns
    ///
    /// The transaction result.
    pub async fn pause_token(&self, data: TokenPausePayload, private_key: &str) -> Result<TransactionResponse> {
        let signature = sign_transaction_payload(&data, private_key)?;
        let request = TokenPauseRequest { data, signature };

        self.post(&api_path(PAUSE), &request).await
    }

    /// Manage token blacklist (add or remove addresses).
    ///
    /// # Arguments
    ///
    /// * `payload` - Token blacklist management parameters
    /// * `private_key` - Private key for signing the transaction (must have
    ///   manage list authority)
    ///
    /// # Returns
    ///
    /// The transaction result.
    pub async fn manage_blacklist(
        &self,
        data: TokenBlacklistPayload,
        private_key: &str,
    ) -> Result<TransactionResponse> {
        let signature = sign_transaction_payload(&data, private_key)?;
        let request = TokenBlacklistRequest { data, signature };

        self.post(&api_path(MANAGE_BLACKLIST), &request).await
    }

    /// Manage token whitelist (add or remove addresses).
    ///
    /// # Arguments
    ///
    /// * `payload` - Token whitelist management parameters
    /// * `private_key` - Private key for signing the transaction (must have
    ///   manage list authority)
    ///
    /// # Returns
    ///
    /// The transaction result.
    pub async fn manage_whitelist(
        &self,
        data: TokenWhitelistPayload,
        private_key: &str,
    ) -> Result<TransactionResponse> {
        let signature = sign_transaction_payload(&data, private_key)?;
        let request = TokenWhitelistRequest { data, signature };

        self.post(&api_path(MANAGE_WHITELIST), &request).await
    }

    /// Update token metadata.
    ///
    /// # Arguments
    ///
    /// * `payload` - Token metadata update parameters
    /// * `private_key` - Private key for signing the transaction (must have
    ///   update metadata authority)
    ///
    /// # Returns
    ///
    /// The transaction result.
    pub async fn update_token_metadata(
        &self,
        data: TokenMetadataUpdatePayload,
        private_key: &str,
    ) -> Result<TransactionResponse> {
        let signature = sign_transaction_payload(&data, private_key)?;
        let request = TokenMetadataUpdateRequest { data, signature };

        self.post(&api_path(UPDATE_METADATA), &request).await
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use alloy_primitives::{Address, U256};
    use om_primitives_types::transaction::payload::{
        Authority, AuthorityAction, BlacklistAction, PauseAction, WhitelistAction,
    };

    use super::*;
    use crate::NamedChain;

    #[test]
    fn test_authority_values() {
        assert_eq!(
            serde_json::to_string(&Authority::MasterMintBurn).expect("Test data should be valid"),
            "\"MasterMintBurn\""
        );
        assert_eq!(
            serde_json::to_string(&Authority::MintBurnTokens).expect("Test data should be valid"),
            "\"MintBurnTokens\""
        );
        assert_eq!(
            serde_json::to_string(&Authority::Pause).expect("Test data should be valid"),
            "\"Pause\""
        );
        assert_eq!(
            serde_json::to_string(&Authority::ManageList).expect("Test data should be valid"),
            "\"ManageList\""
        );
        assert_eq!(
            serde_json::to_string(&Authority::UpdateMetadata).expect("Test data should be valid"),
            "\"UpdateMetadata\""
        );
    }

    #[test]
    fn test_token_mint_payload_structure() {
        let address =
            Address::from_str("0x742d35Cc6634C0532925a3b8D91D6F4A81B8Cbc0").expect("Test data should be valid");
        let token = Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid");

        let payload = TokenMintPayload {
            chain_id: NamedChain::TESTNET_CHAIN_ID,
            nonce: 5,
            recipient: address,
            value: U256::from(1000000000000000000u64),
            token,
        };

        assert_eq!(payload.chain_id, NamedChain::TESTNET_CHAIN_ID);
        assert_eq!(payload.nonce, 5);
        assert_eq!(payload.recipient, address);
        assert_eq!(payload.value, U256::from(1000000000000000000u64));
        assert_eq!(payload.token, token);
    }

    #[test]
    fn test_token_burn_payload_structure() {
        let token = Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid");

        let payload = TokenBurnPayload {
            chain_id: NamedChain::TESTNET_CHAIN_ID,
            nonce: 5,
            value: U256::from(500000000000000000u64),
            token,
        };

        assert_eq!(payload.value, U256::from(500000000000000000u64));
    }

    #[test]
    fn test_authority_action_serialization() {
        assert_eq!(
            serde_json::to_string(&AuthorityAction::Grant).expect("Test data should be valid"),
            "\"Grant\""
        );
        assert_eq!(
            serde_json::to_string(&AuthorityAction::Revoke).expect("Test data should be valid"),
            "\"Revoke\""
        );
    }

    #[test]
    fn test_pause_action_serialization() {
        assert_eq!(
            serde_json::to_string(&PauseAction::Pause).expect("Test data should be valid"),
            "\"Pause\""
        );
        assert_eq!(
            serde_json::to_string(&PauseAction::Unpause).expect("Test data should be valid"),
            "\"Unpause\""
        );
    }

    #[test]
    fn test_blacklist_action_serialization() {
        assert_eq!(
            serde_json::to_string(&BlacklistAction::Add).expect("Test data should be valid"),
            "\"Add\""
        );
        assert_eq!(
            serde_json::to_string(&BlacklistAction::Remove).expect("Test data should be valid"),
            "\"Remove\""
        );
    }

    #[test]
    fn test_whitelist_action_serialization() {
        assert_eq!(
            serde_json::to_string(&WhitelistAction::Add).expect("Test data should be valid"),
            "\"Add\""
        );
        assert_eq!(
            serde_json::to_string(&WhitelistAction::Remove).expect("Test data should be valid"),
            "\"Remove\""
        );
    }

    #[test]
    fn test_token_authority_payload_structure() {
        let authority_address =
            Address::from_str("0x742d35Cc6634C0532925a3b8D91D6F4A81B8Cbc0").expect("Test data should be valid");
        let token = Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid");

        let payload = TokenAuthorityPayload {
            chain_id: NamedChain::TESTNET_CHAIN_ID,
            nonce: 5,
            action: AuthorityAction::Grant,
            authority_type: Authority::MintBurnTokens,
            authority_address,
            token,
            value: U256::from(1000000000000000000u64),
        };

        assert_eq!(payload.action, AuthorityAction::Grant);
        assert_eq!(payload.authority_type, Authority::MintBurnTokens);
        assert_eq!(payload.authority_address, authority_address);
    }

    #[test]
    fn test_token_pause_payload_structure() {
        let token = Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid");

        let payload = TokenPausePayload {
            chain_id: NamedChain::TESTNET_CHAIN_ID,
            nonce: 5,
            action: PauseAction::Pause,
            token,
        };

        assert_eq!(payload.action, PauseAction::Pause);
        assert_eq!(payload.token, token);
    }

    #[test]
    fn test_token_blacklist_payload_structure() {
        let address =
            Address::from_str("0x742d35Cc6634C0532925a3b8D91D6F4A81B8Cbc0").expect("Test data should be valid");
        let token = Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid");

        let payload = TokenBlacklistPayload {
            chain_id: NamedChain::TESTNET_CHAIN_ID,
            nonce: 5,
            action: BlacklistAction::Add,
            address,
            token,
        };

        assert_eq!(payload.action, BlacklistAction::Add);
        assert_eq!(payload.address, address);
        assert_eq!(payload.token, token);
    }

    #[test]
    fn test_token_whitelist_payload_structure() {
        let address =
            Address::from_str("0x742d35Cc6634C0532925a3b8D91D6F4A81B8Cbc0").expect("Test data should be valid");
        let token = Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid");

        let payload = TokenWhitelistPayload {
            chain_id: NamedChain::TESTNET_CHAIN_ID,
            nonce: 5,
            action: WhitelistAction::Add,
            address,
            token,
        };

        assert_eq!(payload.action, WhitelistAction::Add);
        assert_eq!(payload.address, address);
        assert_eq!(payload.token, token);
    }
}
