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

//! # Submit Epoch Change RPC Endpoint
//!
//! This module provides types for the `submitEpochChange` RPC method,
//! which is used to initiate an epoch transition in the network.
//!
//! ## Overview
//!
//! The `submitEpochChange` endpoint is an admin-only RPC that submits
//! a transaction to configure the next epoch's validator set and
//! consensus parameters.
//!
//! ## Example Request
//!
//! ```json
//! {
//!   "jsonrpc": "2.0",
//!   "id": 1,
//!   "method": "submitEpochChange",
//!   "params": [{
//!     "newEpoch": 1,
//!     "validators": [
//!       {
//!         "stake": 1000,
//!         "consensusAddress": "/ip4/127.0.0.1/udp/10100",
//!         "stateSyncAddress": "/ip4/127.0.0.1/udp/20100",
//!         "hostname": "validator-0",
//!         "authorityKey": "...",
//!         "protocolKey": "...",
//!         "networkKey": "...",
//!         "signingKey": "..."
//!       }
//!     ],
//!     "consensusConfig": {
//!       "numLeadersPerRound": 1
//!     }
//!   }]
//! }
//! ```
//!
//! ## Example Response
//!
//! ```json
//! {
//!   "jsonrpc": "2.0",
//!   "id": 1,
//!   "result": {
//!     "success": true,
//!     "message": "Epoch change transaction submitted successfully"
//!   }
//! }
//! ```

use rialo_types::Multiaddr;
use serde::{Deserialize, Serialize};

/// Request to submit an epoch change transaction.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubmitEpochChangeRequest {
    /// The epoch number being transitioned to.
    pub new_epoch: u64,

    /// The validators participating in the new epoch.
    pub validators: Vec<ValidatorInfoRequest>,

    /// Optional consensus configuration overrides for the new epoch.
    #[serde(default)]
    pub consensus_config: Option<EpochConsensusConfigRequest>,
}

/// Validator information in the RPC request.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ValidatorInfoRequest {
    /// Voting power/stake of the validator.
    pub stake: u64,

    /// Network address for consensus protocol communication.
    /// Format: multiaddr (e.g., "/ip4/127.0.0.1/udp/10100")
    pub consensus_address: Multiaddr,

    /// Network address for state sync communication.
    /// Format: multiaddr (e.g., "/ip4/127.0.0.1/udp/20100")
    pub state_sync_address: Multiaddr,

    /// Human-readable hostname for metrics and logging.
    pub hostname: String,

    /// The validator's public key as Rialo identity (base58 encoded).
    pub authority_key: String,

    /// The validator's public key for verifying blocks (base58 encoded).
    pub protocol_key: String,

    /// The validator's public key for TLS and network identity (base58 encoded).
    pub network_key: String,

    /// The validator's public key for signing transactions (base58 encoded).
    pub signing_key: String,
}

/// Consensus configuration parameters in the RPC request.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
// TODO(sub-1495): Rename to EpochFourierConfigRequest
pub struct EpochConsensusConfigRequest {
    /// Number of leaders per round for Mysticeti commits.
    #[serde(default)]
    pub num_leaders_per_round: Option<usize>,

    /// Maximum size of transactions included in a consensus block (bytes).
    #[serde(default)]
    pub max_transactions_in_block_bytes: Option<u64>,

    /// Maximum number of transactions included in a consensus block.
    #[serde(default)]
    pub max_num_transactions_in_block: Option<u64>,

    /// Maximum serialized transaction size (bytes).
    #[serde(default)]
    pub max_transaction_size_bytes: Option<u64>,

    /// Garbage collection depth for consensus.
    #[serde(default)]
    pub gc_depth: Option<u32>,

    /// Enable zstd compression for consensus network.
    #[serde(default)]
    pub zstd_compression: Option<bool>,
}

/// Response from submitting an epoch change transaction.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SubmitEpochChangeResponse {
    /// Whether the transaction was successfully submitted.
    pub success: bool,
}

impl SubmitEpochChangeResponse {
    /// Create a success response.
    pub fn success() -> Self {
        Self { success: true }
    }

    /// Create a failure response with a custom message.
    pub fn failure() -> Self {
        Self { success: false }
    }
}

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

    #[test]
    fn test_submit_epoch_change_request_deserialization() {
        let json = r#"{
            "newEpoch": 1,
            "validators": [
                {
                    "stake": 1000,
                    "consensusAddress": "/ip4/127.0.0.1/udp/10100",
                    "stateSyncAddress": "/ip4/127.0.0.1/udp/20100",
                    "hostname": "validator-0",
                    "authorityKey": "test_authority",
                    "protocolKey": "test_protocol",
                    "networkKey": "test_network",
                    "signingKey": "test_signing"
                }
            ],
            "consensusConfig": {
                "numLeadersPerRound": 2
            }
        }"#;

        let request: SubmitEpochChangeRequest = serde_json::from_str(json).unwrap();
        assert_eq!(request.new_epoch, 1);
        assert_eq!(request.validators.len(), 1);
        assert_eq!(request.validators[0].stake, 1000);
        assert!(request.consensus_config.is_some());
        assert_eq!(
            request
                .consensus_config
                .as_ref()
                .unwrap()
                .num_leaders_per_round,
            Some(2)
        );
    }

    #[test]
    fn test_submit_epoch_change_request_without_config() {
        let json = r#"{
            "newEpoch": 5,
            "validators": []
        }"#;

        let request: SubmitEpochChangeRequest = serde_json::from_str(json).unwrap();
        assert_eq!(request.new_epoch, 5);
        assert!(request.validators.is_empty());
        assert!(request.consensus_config.is_none());
    }

    #[test]
    fn test_response_success() {
        let response = SubmitEpochChangeResponse::success();
        assert!(response.success);
    }

    #[test]
    fn test_response_failure() {
        let response = SubmitEpochChangeResponse::failure();
        assert!(!response.success);
    }
}
