//! RPC client for communicating with synord.

use anyhow::Result;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::{json, Value};

/// RPC client.
#[derive(Clone)]
pub struct RpcClient {
    url: String,
    client: reqwest::Client,
}

impl RpcClient {
    /// Creates a new RPC client.
    pub fn new(url: &str) -> Self {
        RpcClient {
            url: url.to_string(),
            client: reqwest::Client::new(),
        }
    }

    /// Makes an RPC call.
    pub async fn call<T: DeserializeOwned>(&self, method: &str, params: Value) -> Result<T> {
        let request = json!({
            "jsonrpc": "2.0",
            "id": 1,
            "method": method,
            "params": params
        });

        let response = self.client.post(&self.url).json(&request).send().await?;

        let rpc_response: RpcResponse<T> = response.json().await?;

        if let Some(error) = rpc_response.error {
            anyhow::bail!("RPC error {}: {}", error.code, error.message);
        }

        rpc_response
            .result
            .ok_or_else(|| anyhow::anyhow!("No result in response"))
    }

    // ==================== Node Methods ====================

    /// Gets node info.
    pub async fn get_info(&self) -> Result<NodeInfo> {
        self.call("synor_getInfo", json!([])).await
    }

    /// Gets server version.
    pub async fn get_version(&self) -> Result<ServerVersion> {
        self.call("synor_getServerVersion", json!([])).await
    }

    /// Gets connected peers.
    pub async fn get_peer_info(&self) -> Result<Vec<PeerInfo>> {
        self.call("synor_getPeerInfo", json!([])).await
    }

    // ==================== Block Methods ====================

    /// Gets a block by hash.
    pub async fn get_block(&self, hash: &str, include_txs: bool) -> Result<Block> {
        self.call("synor_getBlock", json!([hash, include_txs]))
            .await
    }

    /// Gets block header.
    pub async fn get_block_header(&self, hash: &str) -> Result<BlockHeader> {
        self.call("synor_getBlockHeader", json!([hash])).await
    }

    /// Gets block count.
    pub async fn get_block_count(&self) -> Result<u64> {
        self.call("synor_getBlockCount", json!([])).await
    }

    /// Gets current tips.
    pub async fn get_tips(&self) -> Result<Vec<String>> {
        self.call("synor_getTips", json!([])).await
    }

    /// Gets blue score.
    pub async fn get_blue_score(&self) -> Result<u64> {
        self.call("synor_getBlueScore", json!([])).await
    }

    /// Gets blocks by blue score.
    pub async fn get_blocks_by_blue_score(
        &self,
        blue_score: u64,
        include_txs: bool,
    ) -> Result<Vec<Block>> {
        self.call(
            "synor_getBlocksByBlueScore",
            json!([blue_score, include_txs]),
        )
        .await
    }

    // ==================== Transaction Methods ====================

    /// Gets a transaction.
    pub async fn get_transaction(&self, hash: &str) -> Result<Transaction> {
        self.call("synor_getTransaction", json!([hash])).await
    }

    /// Submits a transaction.
    pub async fn submit_transaction(&self, tx_hex: &str) -> Result<String> {
        self.call("synor_submitTransaction", json!([tx_hex])).await
    }

    /// Gets mempool entries.
    pub async fn get_mempool(&self) -> Result<Vec<MempoolEntry>> {
        self.call("synor_getMempoolEntries", json!([])).await
    }

    /// Estimates fee.
    pub async fn estimate_fee(&self, priority: &str) -> Result<u64> {
        self.call("synor_estimateFee", json!([priority])).await
    }

    // ==================== UTXO Methods ====================

    /// Gets UTXOs for an address.
    pub async fn get_utxos(&self, address: &str) -> Result<Vec<Utxo>> {
        self.call("synor_getUtxosByAddress", json!([address])).await
    }

    /// Gets balance for an address.
    pub async fn get_balance(&self, address: &str) -> Result<Balance> {
        self.call("synor_getBalance", json!([address])).await
    }

    // ==================== Mining Methods ====================

    /// Gets mining info.
    pub async fn get_mining_info(&self) -> Result<MiningInfo> {
        self.call("synor_getMiningInfo", json!([])).await
    }

    /// Gets block template.
    pub async fn get_block_template(&self, address: &str) -> Result<BlockTemplate> {
        self.call("synor_getBlockTemplate", json!([address])).await
    }

    /// Submits a block.
    pub async fn submit_block(&self, block_hex: &str) -> Result<String> {
        self.call("synor_submitBlock", json!([block_hex])).await
    }

    // ==================== Contract Methods ====================

    /// Deploys a contract.
    pub async fn deploy_contract(
        &self,
        bytecode: &str,
        init_args: &str,
        deployer: &str,
        gas_limit: Option<u64>,
    ) -> Result<DeployResult> {
        self.call(
            "synor_deployContract",
            json!({
                "bytecode": bytecode,
                "init_args": init_args,
                "deployer": deployer,
                "gas_limit": gas_limit
            }),
        )
        .await
    }

    /// Calls a contract method.
    pub async fn call_contract(
        &self,
        contract_id: &str,
        method: &str,
        args: &str,
        caller: &str,
        value: u64,
        gas_limit: Option<u64>,
    ) -> Result<ContractResult> {
        self.call(
            "synor_callContract",
            json!({
                "contract_id": contract_id,
                "method": method,
                "args": args,
                "caller": caller,
                "value": value,
                "gas_limit": gas_limit
            }),
        )
        .await
    }

    /// Estimates gas for a contract call.
    pub async fn estimate_gas(
        &self,
        contract_id: &str,
        method: &str,
        args: &str,
        caller: &str,
        value: u64,
    ) -> Result<EstimateGasResult> {
        self.call(
            "synor_estimateGas",
            json!({
                "contract_id": contract_id,
                "method": method,
                "args": args,
                "caller": caller,
                "value": value
            }),
        )
        .await
    }

    /// Gets contract bytecode.
    pub async fn get_contract_code(&self, contract_id: &str) -> Result<GetCodeResult> {
        self.call(
            "synor_getCode",
            json!({
                "contract_id": contract_id
            }),
        )
        .await
    }

    /// Gets contract storage value.
    pub async fn get_contract_storage(
        &self,
        contract_id: &str,
        key: &str,
    ) -> Result<GetStorageResult> {
        self.call(
            "synor_getStorageAt",
            json!({
                "contract_id": contract_id,
                "key": key
            }),
        )
        .await
    }

    /// Gets contract metadata.
    pub async fn get_contract(&self, contract_id: &str) -> Result<ContractInfo> {
        self.call(
            "synor_getContract",
            json!({
                "contract_id": contract_id
            }),
        )
        .await
    }

    // ==================== Network Methods ====================

    /// Adds a peer.
    pub async fn add_peer(&self, address: &str) -> Result<bool> {
        self.call("synor_addPeer", json!([address])).await
    }

    /// Bans a peer.
    pub async fn ban_peer(&self, peer: &str) -> Result<bool> {
        self.call("synor_banPeer", json!([peer])).await
    }

    /// Unbans a peer.
    pub async fn unban_peer(&self, peer: &str) -> Result<bool> {
        self.call("synor_unbanPeer", json!([peer])).await
    }

    // ==================== Governance Methods ====================

    /// Gets governance info.
    pub async fn get_governance_info(&self) -> Result<GovernanceInfo> {
        self.call("synor_getGovernanceInfo", json!([])).await
    }

    /// Gets DAO statistics.
    pub async fn get_dao_stats(&self) -> Result<DaoStats> {
        self.call("synor_getDaoStats", json!([])).await
    }

    /// Gets active proposals.
    pub async fn get_active_proposals(&self) -> Result<Vec<ProposalSummary>> {
        self.call("synor_getActiveProposals", json!([])).await
    }

    /// Gets proposals by state.
    pub async fn get_proposals_by_state(&self, state: &str) -> Result<Vec<ProposalSummary>> {
        self.call("synor_getProposalsByState", json!([state])).await
    }

    /// Gets a proposal by ID.
    pub async fn get_proposal(&self, proposal_id: &str) -> Result<ProposalInfo> {
        self.call("synor_getProposal", json!([proposal_id])).await
    }

    /// Creates a proposal.
    pub async fn create_proposal(
        &self,
        proposer: &str,
        proposal_type: &str,
        title: &str,
        description: &str,
        params: serde_json::Value,
    ) -> Result<CreateProposalResult> {
        self.call(
            "synor_createProposal",
            json!({
                "proposer": proposer,
                "proposal_type": proposal_type,
                "title": title,
                "description": description,
                "params": params
            }),
        )
        .await
    }

    /// Casts a vote on a proposal.
    pub async fn vote(
        &self,
        proposal_id: &str,
        voter: &str,
        choice: &str,
        reason: Option<&str>,
    ) -> Result<VoteResult> {
        self.call(
            "synor_vote",
            json!({
                "proposal_id": proposal_id,
                "voter": voter,
                "choice": choice,
                "reason": reason
            }),
        )
        .await
    }

    /// Executes a passed proposal.
    pub async fn execute_proposal(
        &self,
        proposal_id: &str,
        executor: &str,
    ) -> Result<ExecuteProposalResult> {
        self.call(
            "synor_executeProposal",
            json!({
                "proposal_id": proposal_id,
                "executor": executor
            }),
        )
        .await
    }

    /// Gets treasury pools.
    pub async fn get_treasury_pools(&self) -> Result<Vec<TreasuryPoolInfo>> {
        self.call("synor_getTreasuryPools", json!([])).await
    }

    /// Gets treasury pool by ID.
    pub async fn get_treasury_pool(&self, pool_id: &str) -> Result<TreasuryPoolInfo> {
        self.call("synor_getTreasuryPool", json!([pool_id])).await
    }

    /// Gets total treasury balance.
    pub async fn get_treasury_balance(&self) -> Result<u64> {
        self.call("synor_getTreasuryBalance", json!([])).await
    }
}

// ==================== RPC Types ====================

#[derive(Debug, Deserialize)]
struct RpcResponse<T> {
    result: Option<T>,
    error: Option<RpcError>,
}

#[derive(Debug, Deserialize)]
struct RpcError {
    code: i32,
    message: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct NodeInfo {
    pub version: String,
    pub protocol_version: u32,
    pub network: String,
    pub peer_count: usize,
    pub synced: bool,
    pub block_count: u64,
    pub blue_score: u64,
    pub mempool_size: usize,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ServerVersion {
    pub version: String,
    pub name: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PeerInfo {
    pub id: String,
    pub address: String,
    pub is_inbound: bool,
    pub version: u32,
    pub user_agent: String,
    pub latency_ms: u32,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Block {
    pub hash: String,
    pub header: BlockHeader,
    pub transactions: Vec<Transaction>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct BlockHeader {
    pub version: u32,
    pub parents: Vec<String>,
    pub hash_merkle_root: String,
    pub utxo_commitment: String,
    pub timestamp: u64,
    pub bits: u32,
    pub nonce: u64,
    pub blue_score: u64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Transaction {
    pub hash: String,
    pub inputs: Vec<TxInput>,
    pub outputs: Vec<TxOutput>,
    pub mass: u64,
    pub fee: u64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct TxInput {
    pub previous_outpoint: Outpoint,
    pub signature_script: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Outpoint {
    pub transaction_id: String,
    pub index: u32,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct TxOutput {
    pub value: u64,
    pub script_public_key: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct MempoolEntry {
    pub hash: String,
    pub fee: u64,
    pub mass: u64,
    pub timestamp: u64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Utxo {
    pub outpoint: Outpoint,
    pub amount: u64,
    pub script_public_key: String,
    pub block_hash: Option<String>,
    pub is_coinbase: bool,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Balance {
    pub confirmed: u64,
    pub unconfirmed: u64,
    pub total: u64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct MiningInfo {
    pub blocks: u64,
    pub difficulty: f64,
    pub network_hashrate: f64,
    pub pool_hashrate: Option<f64>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct BlockTemplate {
    pub header: BlockHeader,
    pub transactions: Vec<Transaction>,
    pub target: String,
    pub is_synced: bool,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeployResult {
    pub contract_id: String,
    pub address: String,
    pub gas_used: u64,
    #[serde(default)]
    pub error: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContractResult {
    #[serde(default)]
    pub success: bool,
    #[serde(default)]
    pub data: String,
    #[serde(default)]
    pub gas_used: u64,
    #[serde(default)]
    pub logs: Vec<ContractLog>,
    #[serde(default)]
    pub error: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContractLog {
    pub contract_id: String,
    pub topics: Vec<String>,
    pub data: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EstimateGasResult {
    #[serde(default)]
    pub estimated_gas: u64,
    #[serde(default)]
    pub error: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct GetCodeResult {
    pub code: Option<String>,
    #[serde(default)]
    pub error: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct GetStorageResult {
    pub value: Option<String>,
    #[serde(default)]
    pub error: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContractInfo {
    #[serde(default)]
    pub code_hash: Option<String>,
    #[serde(default)]
    pub deployer: Option<String>,
    #[serde(default)]
    pub deployed_at: Option<u64>,
    #[serde(default)]
    pub deployed_height: Option<u64>,
    #[serde(default)]
    pub error: Option<String>,
}

// ==================== Governance Types ====================

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GovernanceInfo {
    pub proposal_threshold: u64,
    pub quorum_bps: u32,
    pub voting_period_blocks: u64,
    pub execution_delay_blocks: u64,
    pub total_proposals: u64,
    pub active_proposals: u64,
    pub total_treasury_balance: u64,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DaoStats {
    pub total_proposals: u64,
    pub active_proposals: u64,
    pub passed_proposals: u64,
    pub defeated_proposals: u64,
    pub executed_proposals: u64,
    pub total_votes_cast: u64,
    pub council_members: usize,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProposalSummary {
    pub id: String,
    pub number: u64,
    pub title: String,
    pub proposer: String,
    pub state: String,
    pub yes_votes: u64,
    pub no_votes: u64,
    pub abstain_votes: u64,
    pub total_voters: usize,
    pub yes_percentage: f64,
    pub participation_rate: f64,
    pub has_quorum: bool,
    pub time_remaining_blocks: Option<u64>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProposalInfo {
    pub id: String,
    pub number: u64,
    pub proposer: String,
    pub proposal_type: String,
    pub title: String,
    pub description: String,
    pub discussion_url: Option<String>,
    pub created_at_block: u64,
    pub voting_starts_block: u64,
    pub voting_ends_block: u64,
    pub execution_allowed_block: u64,
    pub state: String,
    pub yes_votes: u64,
    pub no_votes: u64,
    pub abstain_votes: u64,
    pub votes: Vec<VoteInfo>,
    #[serde(default)]
    pub error: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VoteInfo {
    pub voter: String,
    pub choice: String,
    pub power: u64,
    pub weight: u64,
    pub voted_at_block: u64,
    pub reason: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateProposalResult {
    pub proposal_id: String,
    pub number: u64,
    #[serde(default)]
    pub error: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VoteResult {
    pub success: bool,
    pub proposal_id: String,
    pub voter: String,
    pub choice: String,
    #[serde(default)]
    pub error: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExecuteProposalResult {
    pub success: bool,
    pub proposal_id: String,
    pub executed_at: u64,
    #[serde(default)]
    pub error: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TreasuryPoolInfo {
    pub id: String,
    pub name: String,
    pub balance: u64,
    pub total_deposited: u64,
    pub total_spent: u64,
    pub frozen: bool,
}
