use ethrex_common::types::{BlockHash, batch::Batch};
use ethrex_rpc::types::block_identifier::BlockIdentifier;
use ethrex_storage::Store;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tracing::debug;

use crate::{
    rpc::{RpcApiContext, RpcHandler},
    utils::RpcErr,
};

#[derive(Serialize, Deserialize)]
pub struct RpcBatch {
    #[serde(flatten)]
    pub batch: Batch,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub block_hashes: Option<Vec<BlockHash>>,
}

impl RpcBatch {
    pub fn build(batch: Batch, block_hashes: bool, store: &Store) -> Result<Self, RpcErr> {
        let block_hashes = if block_hashes {
            Some(get_block_hashes(
                batch.first_block,
                batch.last_block,
                store,
            )?)
        } else {
            None
        };

        Ok(RpcBatch {
            batch,
            block_hashes,
        })
    }
}

fn get_block_hashes(
    first_block: u64,
    last_block: u64,
    store: &Store,
) -> Result<Vec<BlockHash>, RpcErr> {
    let mut block_hashes = Vec::new();
    for block_number in first_block..=last_block {
        let header = store
            .get_block_header(block_number)?
            .ok_or(RpcErr::Internal(format!(
                "Failed to retrieve block header for block number {block_number}"
            )))?;
        let hash = header.hash();
        block_hashes.push(hash);
    }
    Ok(block_hashes)
}

pub struct GetBatchByBatchNumberRequest {
    pub batch_number: u64,
    pub block_hashes: bool,
}

impl RpcHandler for GetBatchByBatchNumberRequest {
    fn parse(params: &Option<Vec<Value>>) -> Result<GetBatchByBatchNumberRequest, RpcErr> {
        let params = params.as_ref().ok_or(ethrex_rpc::RpcErr::BadParams(
            "No params provided".to_owned(),
        ))?;
        if params.len() != 2 {
            return Err(ethrex_rpc::RpcErr::BadParams(
                "Expected 2 params".to_owned(),
            ))?;
        };
        // Parse BatchNumber
        let hex_str = serde_json::from_value::<String>(params[0].clone())
            .map_err(|e| ethrex_rpc::RpcErr::BadParams(e.to_string()))?;

        // Check that the BatchNumber is 0x prefixed
        let hex_str = hex_str
            .strip_prefix("0x")
            .ok_or(ethrex_rpc::RpcErr::BadHexFormat(0))?;

        // Parse hex string
        let batch_number =
            u64::from_str_radix(hex_str, 16).map_err(|_| ethrex_rpc::RpcErr::BadHexFormat(0))?;

        let block_hashes = serde_json::from_value(params[1].clone())?;

        Ok(GetBatchByBatchNumberRequest {
            batch_number,
            block_hashes,
        })
    }

    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
        debug!("Requested batch with number: {}", self.batch_number);
        let l1_fork = context.l1_ctx.blockchain.current_fork().await?;
        let Some(batch) = context
            .rollup_store
            .get_batch(self.batch_number, l1_fork)
            .await?
        else {
            return Ok(Value::Null);
        };
        let rpc_batch = RpcBatch::build(batch, self.block_hashes, &context.l1_ctx.storage)?;

        serde_json::to_value(&rpc_batch).map_err(|error| RpcErr::Internal(error.to_string()))
    }
}

pub struct GetBatchByBatchBlockNumberRequest {
    pub block: BlockIdentifier,
}

impl RpcHandler for GetBatchByBatchBlockNumberRequest {
    fn parse(params: &Option<Vec<Value>>) -> Result<GetBatchByBatchBlockNumberRequest, RpcErr> {
        let params = params.as_ref().ok_or(ethrex_rpc::RpcErr::BadParams(
            "No params provided".to_owned(),
        ))?;
        if params.len() != 1 {
            return Err(ethrex_rpc::RpcErr::BadParams("Expected 1 param".to_owned()))?;
        };
        Ok(GetBatchByBatchBlockNumberRequest {
            block: BlockIdentifier::parse(params[0].clone(), 0)?,
        })
    }

    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
        debug!("Requested batch with block: {}", self.block);
        let l1_fork = context.l1_ctx.blockchain.current_fork().await?;

        let block_number = self
            .block
            .resolve_block_number(&context.l1_ctx.storage)
            .await?
            .ok_or(RpcErr::Internal(format!(
                "Block not found for identifier: {}",
                self.block
            )))?;

        // Gets the batch number for the block
        let batch_number = match context
            .rollup_store
            .get_batch_number_by_block(block_number)
            .await?
        {
            Some(location) => location,
            _ => return Ok(Value::Null),
        };

        let Some(batch) = context
            .rollup_store
            .get_batch(batch_number, l1_fork)
            .await?
        else {
            return Err(RpcErr::Internal(format!(
                "Batch not found for batch number: {}",
                batch_number
            )));
        };

        let rpc_batch = RpcBatch::build(batch, false, &context.l1_ctx.storage)?;

        serde_json::to_value(&rpc_batch).map_err(|error| RpcErr::Internal(error.to_string()))
    }
}

pub struct BatchNumberRequest {}

impl RpcHandler for BatchNumberRequest {
    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
        if params.as_ref().is_some_and(|params| !params.is_empty()) {
            return Err(ethrex_rpc::RpcErr::BadParams(
                "Expected 0 params".to_owned(),
            ))?;
        };

        Ok(BatchNumberRequest {})
    }

    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
        debug!("Requested last batch number");
        let Some(batch_number) = context.rollup_store.get_batch_number().await? else {
            return Ok(Value::Null);
        };

        serde_json::to_value(format!("{batch_number:#x}"))
            .map_err(|error| RpcErr::Internal(error.to_string()))
    }
}
