//! RPC request and response types.

use serde::{Deserialize, Serialize};

/// Block header for RPC responses.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcBlockHeader {
    /// Block hash.
    pub hash: String,
    /// Block version.
    pub version: u32,
    /// Parent hashes (DAG).
    pub parent_hashes: Vec<String>,
    /// Hash merkle root.
    pub hash_merkle_root: String,
    /// Accepted ID merkle root.
    pub accepted_id_merkle_root: String,
    /// UTXO commitment.
    pub utxo_commitment: String,
    /// Block timestamp (Unix ms).
    pub timestamp: u64,
    /// Difficulty bits.
    pub bits: u32,
    /// Mining nonce.
    pub nonce: u64,
    /// DAA score.
    pub daa_score: u64,
    /// Blue score.
    pub blue_score: u64,
    /// Blue work (cumulative).
    pub blue_work: String,
    /// Pruning point.
    pub pruning_point: String,
}

/// Full block for RPC responses.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcBlock {
    /// Block header.
    pub header: RpcBlockHeader,
    /// Transactions in the block.
    pub transactions: Vec<RpcTransaction>,
    /// Verbose data (optional).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub verbose_data: Option<RpcBlockVerboseData>,
}

/// Additional block data for verbose responses.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcBlockVerboseData {
    /// Block hash.
    pub hash: String,
    /// Difficulty as float.
    pub difficulty: f64,
    /// Selected parent hash.
    pub selected_parent_hash: String,
    /// Transaction IDs.
    pub transaction_ids: Vec<String>,
    /// Is header only.
    pub is_header_only: bool,
    /// Blue score.
    pub blue_score: u64,
    /// Children block hashes.
    pub children_hashes: Vec<String>,
    /// Merge set blues.
    pub merge_set_blues_hashes: Vec<String>,
    /// Merge set reds.
    pub merge_set_reds_hashes: Vec<String>,
    /// Is chain block.
    pub is_chain_block: bool,
}

/// Transaction for RPC responses.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcTransaction {
    /// Transaction version.
    pub version: u16,
    /// Transaction inputs.
    pub inputs: Vec<RpcTransactionInput>,
    /// Transaction outputs.
    pub outputs: Vec<RpcTransactionOutput>,
    /// Lock time.
    pub lock_time: u64,
    /// Subnetwork ID.
    pub subnetwork_id: String,
    /// Gas.
    pub gas: u64,
    /// Payload.
    pub payload: String,
    /// Verbose data (optional).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub verbose_data: Option<RpcTransactionVerboseData>,
}

/// Transaction input.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcTransactionInput {
    /// Previous outpoint.
    pub previous_outpoint: RpcOutpoint,
    /// Signature script (hex).
    pub signature_script: String,
    /// Sequence number.
    pub sequence: u64,
    /// Signature operation count.
    pub sig_op_count: u8,
    /// Verbose data (optional).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub verbose_data: Option<RpcTransactionInputVerboseData>,
}

/// Transaction output.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcTransactionOutput {
    /// Value in sompi.
    pub value: u64,
    /// Script public key.
    pub script_public_key: RpcScriptPublicKey,
    /// Verbose data (optional).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub verbose_data: Option<RpcTransactionOutputVerboseData>,
}

/// Outpoint reference.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcOutpoint {
    /// Transaction ID.
    pub transaction_id: String,
    /// Output index.
    pub index: u32,
}

/// Script public key.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcScriptPublicKey {
    /// Script version.
    pub version: u16,
    /// Script (hex).
    pub script: String,
}

/// Verbose transaction data.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcTransactionVerboseData {
    /// Transaction ID.
    pub transaction_id: String,
    /// Transaction hash.
    pub hash: String,
    /// Mass (weight).
    pub mass: u64,
    /// Block hash containing this tx.
    pub block_hash: String,
    /// Block time.
    pub block_time: u64,
}

/// Verbose input data.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcTransactionInputVerboseData {
    /// Address being spent from.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub address: Option<String>,
    /// Value being spent.
    pub value: u64,
}

/// Verbose output data.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcTransactionOutputVerboseData {
    /// Script type.
    pub script_type: String,
    /// Address receiving funds.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub address: Option<String>,
}

/// UTXO entry.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcUtxoEntry {
    /// Amount in sompi.
    pub amount: u64,
    /// Script public key.
    pub script_public_key: RpcScriptPublicKey,
    /// Block DAA score when created.
    pub block_daa_score: u64,
    /// Whether this is a coinbase output.
    pub is_coinbase: bool,
}

/// UTXO with outpoint.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcUtxo {
    /// Outpoint.
    pub outpoint: RpcOutpoint,
    /// UTXO entry.
    pub utxo_entry: RpcUtxoEntry,
}

/// Balance response.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcBalance {
    /// Address.
    pub address: String,
    /// Balance in sompi.
    pub balance: u64,
}

/// Block template for mining.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcBlockTemplate {
    /// Block template data.
    pub block: RpcBlock,
    /// Whether this is a synced template.
    pub is_synced: bool,
}

/// Submit block request.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcSubmitBlockRequest {
    /// The block to submit.
    pub block: RpcBlock,
    /// Allow non-DAA blocks.
    #[serde(default)]
    pub allow_non_daa_blocks: bool,
}

/// Submit block response.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcSubmitBlockResponse {
    /// Result of submission.
    pub report: SubmitBlockReport,
}

/// Submit block report.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum SubmitBlockReport {
    /// Block accepted.
    Success,
    /// Block rejected.
    Reject(RejectReason),
}

/// Block rejection reason.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum RejectReason {
    /// Block is orphan.
    BlockInvalid,
    /// Block already exists.
    IsInIbd,
}

/// Network info.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcNetworkInfo {
    /// Network name.
    pub network_id: String,
    /// Is synced.
    pub is_synced: bool,
    /// Is UTXO indexed.
    pub is_utxo_indexed: bool,
    /// Virtual DAA score.
    pub virtual_daa_score: u64,
    /// Current server time.
    pub server_time: u64,
}

/// Peer info.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcPeerInfo {
    /// Peer ID.
    pub id: String,
    /// Peer address.
    pub address: String,
    /// Last ping duration (ms).
    pub last_ping_duration: u64,
    /// Is outbound connection.
    pub is_outbound: bool,
    /// Time offset.
    pub time_offset: i64,
    /// User agent.
    pub user_agent: String,
    /// Advertised protocol version.
    pub advertised_protocol_version: u32,
    /// Time connected.
    pub time_connected: u64,
    /// Is IBD peer.
    pub is_ibd_peer: bool,
}

/// Connected peers info.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcConnectedPeers {
    /// List of peers.
    pub peer_info: Vec<RpcPeerInfo>,
}

/// Mempool entry.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcMempoolEntry {
    /// Transaction.
    pub transaction: RpcTransaction,
    /// Fee in sompi.
    pub fee: u64,
    /// Time added to mempool.
    pub added_at: u64,
}

/// Mempool info.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcMempoolInfo {
    /// Number of transactions.
    pub size: u64,
    /// Total bytes.
    pub bytes: u64,
    /// Memory usage.
    pub usage: u64,
    /// Max memory.
    pub max_mempool: u64,
}

/// DAG info.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcDagInfo {
    /// Network name.
    pub network: String,
    /// Block count.
    pub block_count: u64,
    /// Header count.
    pub header_count: u64,
    /// Tip hashes.
    pub tip_hashes: Vec<String>,
    /// Virtual parent hashes.
    pub virtual_parent_hashes: Vec<String>,
    /// Difficulty.
    pub difficulty: f64,
    /// Past median time.
    pub past_median_time: u64,
    /// Pruning point hash.
    pub pruning_point_hash: String,
    /// Virtual DAA score.
    pub virtual_daa_score: u64,
}

/// Coin supply info.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcCoinSupply {
    /// Max supply.
    pub max_supply: u64,
    /// Circulating supply.
    pub circulating_supply: u64,
}

/// Fee estimate.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcFeeEstimate {
    /// Priority bucket (sompi per gram).
    pub priority_bucket: FeeEstimateBucket,
    /// Normal bucket.
    pub normal_bucket: FeeEstimateBucket,
    /// Low bucket.
    pub low_bucket: FeeEstimateBucket,
}

/// Fee estimate bucket.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FeeEstimateBucket {
    /// Fee rate.
    pub feerate: f64,
    /// Estimated seconds.
    pub estimated_seconds: f64,
}

/// Server info.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcServerInfo {
    /// RPC API version.
    pub rpc_api_version: u32,
    /// Server version.
    pub server_version: String,
    /// Network ID.
    pub network_id: String,
    /// Has UTXO index.
    pub has_utxo_index: bool,
    /// Is synced.
    pub is_synced: bool,
    /// Virtual DAA score.
    pub virtual_daa_score: u64,
}

/// RPC error codes.
pub mod error_codes {
    /// Parse error.
    pub const PARSE_ERROR: i32 = -32700;
    /// Invalid request.
    pub const INVALID_REQUEST: i32 = -32600;
    /// Method not found.
    pub const METHOD_NOT_FOUND: i32 = -32601;
    /// Invalid params.
    pub const INVALID_PARAMS: i32 = -32602;
    /// Internal error.
    pub const INTERNAL_ERROR: i32 = -32603;
    /// Block not found.
    pub const BLOCK_NOT_FOUND: i32 = -1;
    /// Transaction not found.
    pub const TX_NOT_FOUND: i32 = -2;
    /// Address not found.
    pub const ADDRESS_NOT_FOUND: i32 = -3;
    /// Not synced.
    pub const NOT_SYNCED: i32 = -4;
}

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

    #[test]
    fn test_rpc_outpoint_serialization() {
        let outpoint = RpcOutpoint {
            transaction_id: "abc123".to_string(),
            index: 0,
        };

        let json = serde_json::to_string(&outpoint).unwrap();
        assert!(json.contains("transactionId"));
        assert!(json.contains("index"));
    }

    #[test]
    fn test_rpc_balance_serialization() {
        let balance = RpcBalance {
            address: "synor:abc123".to_string(),
            balance: 1000000000,
        };

        let json = serde_json::to_string(&balance).unwrap();
        let parsed: RpcBalance = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.balance, balance.balance);
    }

    #[test]
    fn test_rpc_block_header_serialization() {
        let header = RpcBlockHeader {
            hash: "abc123".to_string(),
            version: 1,
            parent_hashes: vec!["parent1".to_string()],
            hash_merkle_root: "merkle".to_string(),
            accepted_id_merkle_root: "accepted".to_string(),
            utxo_commitment: "utxo".to_string(),
            timestamp: 1234567890,
            bits: 0x1d00ffff,
            nonce: 12345,
            daa_score: 100,
            blue_score: 50,
            blue_work: "1000".to_string(),
            pruning_point: "prune".to_string(),
        };

        let json = serde_json::to_string(&header).unwrap();
        assert!(json.contains("hashMerkleRoot")); // camelCase
        assert!(json.contains("blueScore"));

        let parsed: RpcBlockHeader = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.version, 1);
        assert_eq!(parsed.daa_score, 100);
    }

    #[test]
    fn test_rpc_transaction_input_serialization() {
        let input = RpcTransactionInput {
            previous_outpoint: RpcOutpoint {
                transaction_id: "txid".to_string(),
                index: 0,
            },
            signature_script: "0102030405".to_string(),
            sequence: 0xffffffff,
            sig_op_count: 1,
            verbose_data: None,
        };

        let json = serde_json::to_string(&input).unwrap();
        assert!(json.contains("previousOutpoint"));
        assert!(json.contains("signatureScript"));
        assert!(!json.contains("verboseData")); // None should be skipped

        let parsed: RpcTransactionInput = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.sequence, 0xffffffff);
    }

    #[test]
    fn test_rpc_transaction_output_serialization() {
        let output = RpcTransactionOutput {
            value: 1_000_000_000,
            script_public_key: RpcScriptPublicKey {
                version: 0,
                script: "76a914...88ac".to_string(),
            },
            verbose_data: Some(RpcTransactionOutputVerboseData {
                script_type: "p2pkh".to_string(),
                address: Some("synor:qz...".to_string()),
            }),
        };

        let json = serde_json::to_string(&output).unwrap();
        assert!(json.contains("scriptPublicKey"));
        assert!(json.contains("verboseData"));

        let parsed: RpcTransactionOutput = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.value, 1_000_000_000);
        assert!(parsed.verbose_data.is_some());
    }

    #[test]
    fn test_rpc_utxo_entry_serialization() {
        let utxo = RpcUtxoEntry {
            amount: 5_000_000_000,
            script_public_key: RpcScriptPublicKey {
                version: 0,
                script: "76a914...88ac".to_string(),
            },
            block_daa_score: 100,
            is_coinbase: false,
        };

        let json = serde_json::to_string(&utxo).unwrap();
        assert!(json.contains("blockDaaScore"));
        assert!(json.contains("isCoinbase"));

        let parsed: RpcUtxoEntry = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.amount, 5_000_000_000);
        assert!(!parsed.is_coinbase);
    }

    #[test]
    fn test_rpc_network_info_serialization() {
        let info = RpcNetworkInfo {
            network_id: "testnet".to_string(),
            is_synced: true,
            is_utxo_indexed: true,
            virtual_daa_score: 1000,
            server_time: 1234567890,
        };

        let json = serde_json::to_string(&info).unwrap();
        assert!(json.contains("networkId"));
        assert!(json.contains("isSynced"));

        let parsed: RpcNetworkInfo = serde_json::from_str(&json).unwrap();
        assert!(parsed.is_synced);
    }

    #[test]
    fn test_rpc_peer_info_serialization() {
        let peer = RpcPeerInfo {
            id: "12D3KooW...".to_string(),
            address: "/ip4/192.168.1.1/tcp/16111".to_string(),
            last_ping_duration: 50,
            is_outbound: true,
            time_offset: -100,
            user_agent: "synor/0.1.0".to_string(),
            advertised_protocol_version: 1,
            time_connected: 3600,
            is_ibd_peer: false,
        };

        let json = serde_json::to_string(&peer).unwrap();
        assert!(json.contains("lastPingDuration"));
        assert!(json.contains("isOutbound"));

        let parsed: RpcPeerInfo = serde_json::from_str(&json).unwrap();
        assert!(parsed.is_outbound);
        assert_eq!(parsed.time_offset, -100);
    }

    #[test]
    fn test_submit_block_report_serialization() {
        let success = SubmitBlockReport::Success;
        let json = serde_json::to_string(&success).unwrap();
        assert_eq!(json, "\"success\"");

        let reject = SubmitBlockReport::Reject(RejectReason::BlockInvalid);
        let json = serde_json::to_string(&reject).unwrap();
        assert!(json.contains("blockInvalid"));
    }

    #[test]
    fn test_rpc_mempool_info_serialization() {
        let info = RpcMempoolInfo {
            size: 100,
            bytes: 50000,
            usage: 75000,
            max_mempool: 300_000_000,
        };

        let json = serde_json::to_string(&info).unwrap();
        assert!(json.contains("maxMempool"));

        let parsed: RpcMempoolInfo = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.size, 100);
    }

    #[test]
    fn test_rpc_dag_info_serialization() {
        let dag = RpcDagInfo {
            network: "mainnet".to_string(),
            block_count: 10000,
            header_count: 10000,
            tip_hashes: vec!["tip1".to_string(), "tip2".to_string()],
            virtual_parent_hashes: vec!["vp1".to_string()],
            difficulty: 12345.678,
            past_median_time: 1234567890,
            pruning_point_hash: "prune".to_string(),
            virtual_daa_score: 5000,
        };

        let json = serde_json::to_string(&dag).unwrap();
        assert!(json.contains("blockCount"));
        assert!(json.contains("tipHashes"));

        let parsed: RpcDagInfo = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.tip_hashes.len(), 2);
    }

    #[test]
    fn test_rpc_coin_supply_serialization() {
        let supply = RpcCoinSupply {
            max_supply: 70_000_000 * 100_000_000,
            circulating_supply: 10_000_000 * 100_000_000,
        };

        let json = serde_json::to_string(&supply).unwrap();
        assert!(json.contains("maxSupply"));
        assert!(json.contains("circulatingSupply"));

        let parsed: RpcCoinSupply = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.max_supply, 70_000_000 * 100_000_000);
    }

    #[test]
    fn test_rpc_fee_estimate_serialization() {
        let estimate = RpcFeeEstimate {
            priority_bucket: FeeEstimateBucket {
                feerate: 10.0,
                estimated_seconds: 0.1,
            },
            normal_bucket: FeeEstimateBucket {
                feerate: 5.0,
                estimated_seconds: 1.0,
            },
            low_bucket: FeeEstimateBucket {
                feerate: 1.0,
                estimated_seconds: 10.0,
            },
        };

        let json = serde_json::to_string(&estimate).unwrap();
        assert!(json.contains("priorityBucket"));
        assert!(json.contains("feerate"));

        let parsed: RpcFeeEstimate = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.priority_bucket.feerate, 10.0);
    }

    #[test]
    fn test_rpc_server_info_serialization() {
        let info = RpcServerInfo {
            rpc_api_version: 1,
            server_version: "0.1.0".to_string(),
            network_id: "testnet".to_string(),
            has_utxo_index: true,
            is_synced: true,
            virtual_daa_score: 1000,
        };

        let json = serde_json::to_string(&info).unwrap();
        assert!(json.contains("rpcApiVersion"));
        assert!(json.contains("serverVersion"));

        let parsed: RpcServerInfo = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.rpc_api_version, 1);
    }

    #[test]
    fn test_error_codes() {
        assert_eq!(error_codes::PARSE_ERROR, -32700);
        assert_eq!(error_codes::INVALID_REQUEST, -32600);
        assert_eq!(error_codes::METHOD_NOT_FOUND, -32601);
        assert_eq!(error_codes::INVALID_PARAMS, -32602);
        assert_eq!(error_codes::INTERNAL_ERROR, -32603);
        assert_eq!(error_codes::BLOCK_NOT_FOUND, -1);
        assert_eq!(error_codes::TX_NOT_FOUND, -2);
        assert_eq!(error_codes::ADDRESS_NOT_FOUND, -3);
        assert_eq!(error_codes::NOT_SYNCED, -4);
    }
}
