use core::str::FromStr;
use ibc::clients::tendermint::consensus_state::ConsensusState as TmConsensusState;
use ibc::clients::tendermint::types::proto::v1::Header as RawHeader;
use ibc::clients::tendermint::types::{Header, TENDERMINT_HEADER_TYPE_URL};
use ibc::core::client::types::error::ClientError;
use ibc::core::client::types::Height;
use ibc::core::host::types::identifiers::ChainId;
use ibc::core::primitives::prelude::*;
use ibc::core::primitives::Timestamp;
use ibc::primitives::proto::{Any, Protobuf};
use tendermint::block::Header as TmHeader;
use tendermint::validator::Set as ValidatorSet;
use tendermint_testgen::light_block::TmLightBlock;
use tendermint_testgen::{
    Generator, Header as TestgenHeader, LightBlock as TestgenLightBlock,
    Validator as TestgenValidator,
};
use crate::testapp::ibc::clients::mock::consensus_state::MockConsensusState;
use crate::testapp::ibc::clients::mock::header::MockHeader;
use crate::testapp::ibc::clients::AnyConsensusState;
#[derive(Clone, Debug, Copy)]
pub enum HostType {
    Mock,
    SyntheticTendermint,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SyntheticTmBlock {
    pub trusted_height: Height,
    pub trusted_next_validators: ValidatorSet,
    pub light_block: TmLightBlock,
}
impl SyntheticTmBlock {
    pub fn header(&self) -> &TmHeader {
        &self.light_block.signed_header.header
    }
}
impl From<SyntheticTmBlock> for Header {
    fn from(light_block: SyntheticTmBlock) -> Self {
        let SyntheticTmBlock {
            trusted_height,
            trusted_next_validators,
            light_block,
        } = light_block;
        Self {
            signed_header: light_block.signed_header,
            validator_set: light_block.validators,
            trusted_height,
            trusted_next_validator_set: trusted_next_validators,
        }
    }
}
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HostBlock {
    Mock(Box<MockHeader>),
    SyntheticTendermint(Box<SyntheticTmBlock>),
}
impl HostBlock {
    pub fn height(&self) -> Height {
        match self {
            HostBlock::Mock(header) => header.height(),
            HostBlock::SyntheticTendermint(light_block) => Height::new(
                ChainId::from_str(light_block.header().chain_id.as_str())
                    .expect("Never fails")
                    .revision_number(),
                light_block.header().height.value(),
            )
            .expect("Never fails"),
        }
    }
    pub fn set_trusted_height(&mut self, height: Height) {
        match self {
            HostBlock::Mock(_) => {}
            HostBlock::SyntheticTendermint(light_block) => light_block.trusted_height = height,
        }
    }
    pub fn set_trusted_next_validators_set(&mut self, trusted_next_validators: ValidatorSet) {
        match self {
            HostBlock::Mock(_) => {}
            HostBlock::SyntheticTendermint(light_block) => {
                light_block.trusted_next_validators = trusted_next_validators
            }
        }
    }
    pub fn timestamp(&self) -> Timestamp {
        match self {
            HostBlock::Mock(header) => header.timestamp,
            HostBlock::SyntheticTendermint(light_block) => light_block.header().time.into(),
        }
    }
    pub fn generate_block(
        chain_id: ChainId,
        chain_type: HostType,
        height: u64,
        timestamp: Timestamp,
    ) -> HostBlock {
        match chain_type {
            HostType::Mock => HostBlock::Mock(Box::new(MockHeader {
                height: Height::new(chain_id.revision_number(), height).expect("Never fails"),
                timestamp,
            })),
            HostType::SyntheticTendermint => HostBlock::SyntheticTendermint(Box::new(
                Self::generate_tm_block(chain_id, height, timestamp),
            )),
        }
    }
    pub fn generate_block_with_validators(
        chain_id: ChainId,
        chain_type: HostType,
        height: u64,
        timestamp: Timestamp,
        validators: &[TestgenValidator],
        next_validators: &[TestgenValidator],
    ) -> HostBlock {
        match chain_type {
            HostType::Mock => HostBlock::Mock(Box::new(MockHeader {
                height: Height::new(chain_id.revision_number(), height).expect("Never fails"),
                timestamp,
            })),
            HostType::SyntheticTendermint => {
                let light_block = TestgenLightBlock::new_default_with_header(
                    TestgenHeader::new(validators)
                        .height(height)
                        .chain_id(chain_id.as_str())
                        .next_validators(next_validators)
                        .time(timestamp.into_tm_time().expect("Never fails")),
                )
                .validators(validators)
                .next_validators(next_validators)
                .generate()
                .expect("Never fails");
                HostBlock::SyntheticTendermint(Box::new(SyntheticTmBlock {
                    trusted_height: Height::new(chain_id.revision_number(), 1)
                        .expect("Never fails"),
                    trusted_next_validators: light_block.next_validators.clone(),
                    light_block,
                }))
            }
        }
    }
    pub fn generate_tm_block(
        chain_id: ChainId,
        height: u64,
        timestamp: Timestamp,
    ) -> SyntheticTmBlock {
        let validators = [
            TestgenValidator::new("1").voting_power(50),
            TestgenValidator::new("2").voting_power(50),
        ];
        let header = TestgenHeader::new(&validators)
            .height(height)
            .chain_id(chain_id.as_str())
            .next_validators(&validators)
            .time(timestamp.into_tm_time().expect("Never fails"));
        let light_block = TestgenLightBlock::new_default_with_header(header)
            .generate()
            .expect("Never fails");
        SyntheticTmBlock {
            trusted_height: Height::new(chain_id.revision_number(), 1).expect("Never fails"),
            trusted_next_validators: light_block.next_validators.clone(),
            light_block,
        }
    }
    pub fn try_into_tm_block(self) -> Option<SyntheticTmBlock> {
        match self {
            HostBlock::Mock(_) => None,
            HostBlock::SyntheticTendermint(tm_block) => Some(*tm_block),
        }
    }
}
impl From<SyntheticTmBlock> for AnyConsensusState {
    fn from(light_block: SyntheticTmBlock) -> Self {
        let cs = TmConsensusState::from(light_block.header().clone());
        cs.into()
    }
}
impl From<HostBlock> for AnyConsensusState {
    fn from(any_block: HostBlock) -> Self {
        match any_block {
            HostBlock::Mock(mock_header) => MockConsensusState::new(*mock_header).into(),
            HostBlock::SyntheticTendermint(light_block) => {
                TmConsensusState::from(light_block.header().clone()).into()
            }
        }
    }
}
impl Protobuf<Any> for HostBlock {}
impl TryFrom<Any> for HostBlock {
    type Error = ClientError;
    fn try_from(_raw: Any) -> Result<Self, Self::Error> {
        todo!()
    }
}
impl From<HostBlock> for Any {
    fn from(value: HostBlock) -> Self {
        fn encode_light_block(light_block: SyntheticTmBlock) -> Vec<u8> {
            use prost::Message;
            let SyntheticTmBlock {
                trusted_height,
                trusted_next_validators,
                light_block,
            } = light_block;
            RawHeader {
                signed_header: Some(light_block.signed_header.into()),
                validator_set: Some(light_block.validators.into()),
                trusted_height: Some(trusted_height.into()),
                trusted_validators: Some(trusted_next_validators.into()),
            }
            .encode_to_vec()
        }
        match value {
            HostBlock::Mock(mock_header) => (*mock_header).into(),
            HostBlock::SyntheticTendermint(light_block) => Self {
                type_url: TENDERMINT_HEADER_TYPE_URL.to_string(),
                value: encode_light_block(*light_block),
            },
        }
    }
}