// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use rialo_shared_types::AccountInfoResponseValue;
use serde::{Deserialize, Serialize};
use validator::Validate;

use super::rpc_response_context::RpcResponseContext;

/// Filter for getAccountsByOwner to specify what kind of accounts to return.
///
/// This enum defines the different types of account queries supported:
/// - `ProgramAccounts`: Query accounts by their on-chain owner (program ID)
/// - `TokenAccounts`: Query token accounts by the wallet that holds the tokens (future)
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase", tag = "type")]
pub enum AccountFilter {
    /// Return accounts owned by a program.
    ///
    /// The `owner` parameter in the request should be a program ID.
    /// Returns all accounts where `account.owner == program_id`.
    #[default]
    #[serde(rename = "programAccounts")]
    ProgramAccounts,

    /// Return token accounts belonging to a wallet.
    /// Use this to get "all tokens/NFTs for a wallet".
    /// NFTs are automatically included since they are also token accounts.
    ///
    /// The `owner` parameter in the request should be a wallet address.
    /// Returns all token accounts where the token struct's owner field matches.
    ///
    /// Note: This filter is planned for Phase 2 and requires additional indexer changes.
    #[serde(rename = "tokenAccounts")]
    TokenAccounts {
        /// Optional: filter by specific mint address (for performance).
        /// Returns the wallet's token account(s) for this specific mint only.
        ///
        /// While technically possible to have multiple token accounts per mint,
        /// in practice Associated Token Accounts (ATAs) are used exclusively,
        /// meaning each wallet has at most ONE account per mint.
        #[serde(skip_serializing_if = "Option::is_none")]
        mint: Option<String>,
    },
    // Future extensions:
    // StakeAccounts,
    // NonceAccounts,
}

/// Configuration for getAccountsByOwner request.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct GetAccountsByOwnerConfig {
    /// Maximum number of accounts to return (used with indexer).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<usize>,

    /// Cursor for pagination: accounts after this pubkey (base58-encoded).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub after: Option<String>,
}

impl GetAccountsByOwnerConfig {
    pub fn new() -> Self {
        Self {
            limit: None,
            after: None,
        }
    }

    /// Create config with pagination parameters.
    pub fn with_pagination(limit: Option<usize>, after: Option<String>) -> Self {
        Self { limit, after }
    }
}

/// Request parameters for the `getAccountsByOwner` RPC method.
///
/// This method returns accounts based on the filter type:
/// - For `ProgramAccounts`: Returns all accounts owned by a given program
/// - For `TokenAccounts`: Returns all token accounts belonging to a wallet (future)
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
#[serde(rename_all = "camelCase")]
pub struct GetAccountsByOwnerRequest {
    /// The owner pubkey to query accounts for.
    /// - For ProgramAccounts: this is the program ID
    /// - For TokenAccounts: this is the wallet address
    #[validate(length(min = 1))]
    pub owner: String,

    /// Filter to specify what kind of accounts to return.
    #[serde(default)]
    pub filter: AccountFilter,

    /// Optional configuration for pagination.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub config: Option<GetAccountsByOwnerConfig>,
}

impl GetAccountsByOwnerRequest {
    /// Create a new request for program accounts (default filter).
    pub fn new(owner: String) -> Self {
        Self {
            owner,
            filter: AccountFilter::default(),
            config: Some(GetAccountsByOwnerConfig::default()),
        }
    }

    /// Create a new request with a specific filter.
    pub fn with_filter(owner: String, filter: AccountFilter) -> Self {
        Self {
            owner,
            filter,
            config: Some(GetAccountsByOwnerConfig::default()),
        }
    }

    /// Create a new request with filter and config.
    pub fn with_config(
        owner: String,
        filter: AccountFilter,
        config: GetAccountsByOwnerConfig,
    ) -> Self {
        Self {
            owner,
            filter,
            config: Some(config),
        }
    }
}

/// A single account returned by getAccountsByOwner.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OwnerAccount {
    /// The account's public key (base58-encoded).
    pub pubkey: String,
    /// The account information.
    pub account: AccountInfoResponseValue,
}

/// Pagination information for responses.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaginationInfo {
    /// Whether there are more results after this page.
    pub has_more: bool,
    /// Cursor for the next page (base58-encoded pubkey), if has_more is true.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub next_cursor: Option<String>,
}

/// Response from the `getAccountsByOwner` RPC method.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetAccountsByOwnerResponse {
    /// Response context with slot information.
    pub context: RpcResponseContext,
    /// Array of accounts matching the filter criteria.
    pub value: Vec<OwnerAccount>,
    /// Pagination info (only present when using indexed queries with pagination).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pagination: Option<PaginationInfo>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_get_accounts_by_owner_request_serialization() {
        let request =
            GetAccountsByOwnerRequest::new("11111111111111111111111111111111".to_string());
        let json = serde_json::to_string(&request).unwrap();
        assert!(json.contains("11111111111111111111111111111111"));
        assert!(json.contains("programAccounts"));
    }

    #[test]
    fn test_get_accounts_by_owner_config() {
        let config = GetAccountsByOwnerConfig::new();
        assert_eq!(config.limit, None);
        assert_eq!(config.after, None);
    }

    #[test]
    fn test_account_filter_default() {
        let filter = AccountFilter::default();
        assert_eq!(filter, AccountFilter::ProgramAccounts);
    }

    #[test]
    fn test_program_accounts_filter_serialization() {
        let filter = AccountFilter::ProgramAccounts;
        let json = serde_json::to_string(&filter).unwrap();
        assert_eq!(json, r#"{"type":"programAccounts"}"#);
    }

    #[test]
    fn test_token_accounts_filter_serialization() {
        let filter = AccountFilter::TokenAccounts { mint: None };
        let json = serde_json::to_string(&filter).unwrap();
        assert_eq!(json, r#"{"type":"tokenAccounts"}"#);

        let filter_with_mint = AccountFilter::TokenAccounts {
            mint: Some("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string()),
        };
        let json_with_mint = serde_json::to_string(&filter_with_mint).unwrap();
        assert!(json_with_mint.contains("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"));
    }

    #[test]
    fn test_account_filter_deserialization() {
        let json = r#"{"type":"programAccounts"}"#;
        let filter: AccountFilter = serde_json::from_str(json).unwrap();
        assert_eq!(filter, AccountFilter::ProgramAccounts);

        let json = r#"{"type":"tokenAccounts"}"#;
        let filter: AccountFilter = serde_json::from_str(json).unwrap();
        assert!(matches!(
            filter,
            AccountFilter::TokenAccounts { mint: None }
        ));

        let json = r#"{"type":"tokenAccounts","mint":"ABC123"}"#;
        let filter: AccountFilter = serde_json::from_str(json).unwrap();
        assert!(matches!(
            filter,
            AccountFilter::TokenAccounts {
                mint: Some(m)
            } if m == "ABC123"
        ));
    }

    #[test]
    fn test_request_with_filter() {
        let request = GetAccountsByOwnerRequest::with_filter(
            "MyWallet111111111111111111111111111111111111".to_string(),
            AccountFilter::TokenAccounts {
                mint: Some("USDC1111111111111111111111111111111111111111".to_string()),
            },
        );
        let json = serde_json::to_string(&request).unwrap();
        assert!(json.contains("tokenAccounts"));
        assert!(json.contains("USDC1111111111111111111111111111111111111111"));
    }
}
