//! IBC Light Client
//!
//! Light clients enable trustless verification of state from remote chains.
//! Each chain type has its own light client implementation.

use crate::error::{IbcError, IbcResult};
use crate::types::{ChainId, Height, Timestamp};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;

/// Client identifier
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ClientId(pub String);

impl ClientId {
    /// Create a new client ID
    pub fn new(client_type: &str, sequence: u64) -> Self {
        Self(format!("{}-{}", client_type, sequence))
    }

    /// Parse client type from ID
    pub fn client_type(&self) -> Option<&str> {
        self.0.rsplit('-').nth(1)
    }

    /// Parse sequence from ID
    pub fn sequence(&self) -> Option<u64> {
        self.0.rsplit('-').next()?.parse().ok()
    }
}

impl fmt::Display for ClientId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Light client type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ClientType {
    /// Tendermint/CometBFT light client (Cosmos chains)
    Tendermint,
    /// GRANDPA light client (Polkadot/Substrate)
    Grandpa,
    /// Synor native light client
    Synor,
    /// Solo machine (single signer)
    SoloMachine,
    /// Localhost (same chain, for testing)
    Localhost,
}

impl ClientType {
    /// Get type name
    pub fn as_str(&self) -> &'static str {
        match self {
            ClientType::Tendermint => "07-tendermint",
            ClientType::Grandpa => "10-grandpa",
            ClientType::Synor => "synor",
            ClientType::SoloMachine => "06-solomachine",
            ClientType::Localhost => "09-localhost",
        }
    }
}

impl fmt::Display for ClientType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

/// Client state - represents the state of a light client
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientState {
    /// Client type
    pub client_type: ClientType,
    /// Chain ID of the counterparty
    pub chain_id: ChainId,
    /// Trust level (numerator/denominator)
    pub trust_level: TrustLevel,
    /// Trusting period in nanoseconds
    pub trusting_period: u64,
    /// Unbonding period in nanoseconds
    pub unbonding_period: u64,
    /// Max clock drift in nanoseconds
    pub max_clock_drift: u64,
    /// Latest height
    pub latest_height: Height,
    /// Frozen height (None if not frozen)
    pub frozen_height: Option<Height>,
    /// Whether upgrades are allowed
    pub allow_update_after_expiry: bool,
    /// Whether misbehaviour upgrades are allowed
    pub allow_update_after_misbehaviour: bool,
}

impl ClientState {
    /// Create a new Tendermint client state
    pub fn new_tendermint(
        chain_id: ChainId,
        trust_level: TrustLevel,
        trusting_period: u64,
        unbonding_period: u64,
        max_clock_drift: u64,
        latest_height: Height,
    ) -> Self {
        Self {
            client_type: ClientType::Tendermint,
            chain_id,
            trust_level,
            trusting_period,
            unbonding_period,
            max_clock_drift,
            latest_height,
            frozen_height: None,
            allow_update_after_expiry: false,
            allow_update_after_misbehaviour: false,
        }
    }

    /// Create a new Synor client state
    pub fn new_synor(chain_id: ChainId, latest_height: Height) -> Self {
        Self {
            client_type: ClientType::Synor,
            chain_id,
            trust_level: TrustLevel::default(),
            trusting_period: 14 * 24 * 60 * 60 * 1_000_000_000, // 14 days
            unbonding_period: 21 * 24 * 60 * 60 * 1_000_000_000, // 21 days
            max_clock_drift: 10 * 1_000_000_000, // 10 seconds
            latest_height,
            frozen_height: None,
            allow_update_after_expiry: true,
            allow_update_after_misbehaviour: false,
        }
    }

    /// Check if client is frozen
    pub fn is_frozen(&self) -> bool {
        self.frozen_height.is_some()
    }

    /// Check if client is expired
    pub fn is_expired(&self, current_time: Timestamp, consensus_time: Timestamp) -> bool {
        let elapsed = current_time.nanoseconds().saturating_sub(consensus_time.nanoseconds());
        elapsed > self.trusting_period
    }

    /// Freeze the client at a height
    pub fn freeze(&mut self, height: Height) {
        self.frozen_height = Some(height);
    }

    /// Verify the client state is valid
    pub fn validate(&self) -> IbcResult<()> {
        if self.trusting_period >= self.unbonding_period {
            return Err(IbcError::InvalidClientState(
                "trusting period must be less than unbonding period".to_string(),
            ));
        }

        if self.latest_height.is_zero() {
            return Err(IbcError::InvalidClientState(
                "latest height cannot be zero".to_string(),
            ));
        }

        self.trust_level.validate()?;

        Ok(())
    }
}

/// Trust level (fraction)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct TrustLevel {
    /// Numerator
    pub numerator: u64,
    /// Denominator
    pub denominator: u64,
}

impl TrustLevel {
    /// Create a new trust level
    pub fn new(numerator: u64, denominator: u64) -> Self {
        Self {
            numerator,
            denominator,
        }
    }

    /// Default trust level (1/3)
    pub fn one_third() -> Self {
        Self {
            numerator: 1,
            denominator: 3,
        }
    }

    /// Two-thirds trust level
    pub fn two_thirds() -> Self {
        Self {
            numerator: 2,
            denominator: 3,
        }
    }

    /// Validate the trust level
    pub fn validate(&self) -> IbcResult<()> {
        if self.denominator == 0 {
            return Err(IbcError::InvalidClientState(
                "trust level denominator cannot be zero".to_string(),
            ));
        }

        if self.numerator == 0 || self.numerator > self.denominator {
            return Err(IbcError::InvalidClientState(
                "trust level must be between 0 and 1".to_string(),
            ));
        }

        Ok(())
    }

    /// Check if a voting power meets the trust threshold
    pub fn check(&self, signed_power: u64, total_power: u64) -> bool {
        if total_power == 0 {
            return false;
        }
        // signed_power / total_power >= numerator / denominator
        // signed_power * denominator >= numerator * total_power
        signed_power * self.denominator >= self.numerator * total_power
    }
}

impl Default for TrustLevel {
    fn default() -> Self {
        Self::one_third()
    }
}

/// Consensus state - represents the consensus state at a specific height
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsensusState {
    /// Timestamp of this state
    pub timestamp: Timestamp,
    /// Root hash of the state (commitment root)
    pub root: Vec<u8>,
    /// Next validators hash (for Tendermint)
    pub next_validators_hash: Vec<u8>,
}

impl ConsensusState {
    /// Create a new consensus state
    pub fn new(timestamp: Timestamp, root: Vec<u8>, next_validators_hash: Vec<u8>) -> Self {
        Self {
            timestamp,
            root,
            next_validators_hash,
        }
    }

    /// Get the root as a fixed-size array
    pub fn root_bytes(&self) -> [u8; 32] {
        let mut bytes = [0u8; 32];
        let len = self.root.len().min(32);
        bytes[..len].copy_from_slice(&self.root[..len]);
        bytes
    }
}

/// Header for updating client state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Header {
    /// Height of this header
    pub height: Height,
    /// Timestamp
    pub timestamp: Timestamp,
    /// Signed header data
    pub signed_header: SignedHeader,
    /// Validator set
    pub validator_set: ValidatorSet,
    /// Trusted height (for verification)
    pub trusted_height: Height,
    /// Trusted validators
    pub trusted_validators: ValidatorSet,
}

/// Signed header
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignedHeader {
    /// Block hash
    pub block_hash: Vec<u8>,
    /// App hash (state root)
    pub app_hash: Vec<u8>,
    /// Commit signatures
    pub commit: Commit,
}

/// Commit with signatures
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Commit {
    /// Height
    pub height: Height,
    /// Block ID
    pub block_id: Vec<u8>,
    /// Signatures
    pub signatures: Vec<CommitSig>,
}

/// Commit signature
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitSig {
    /// Validator address
    pub validator_address: Vec<u8>,
    /// Timestamp
    pub timestamp: Timestamp,
    /// Signature bytes
    pub signature: Vec<u8>,
}

/// Validator set
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidatorSet {
    /// Validators
    pub validators: Vec<Validator>,
    /// Proposer address
    pub proposer: Option<Vec<u8>>,
    /// Total voting power
    pub total_voting_power: u64,
}

impl ValidatorSet {
    /// Create an empty validator set
    pub fn empty() -> Self {
        Self {
            validators: Vec::new(),
            proposer: None,
            total_voting_power: 0,
        }
    }

    /// Calculate total voting power
    pub fn calculate_total_power(&mut self) {
        self.total_voting_power = self.validators.iter().map(|v| v.voting_power).sum();
    }

    /// Get validator by address
    pub fn get_by_address(&self, address: &[u8]) -> Option<&Validator> {
        self.validators.iter().find(|v| v.address == address)
    }
}

/// Validator
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Validator {
    /// Address
    pub address: Vec<u8>,
    /// Public key
    pub pub_key: Vec<u8>,
    /// Voting power
    pub voting_power: u64,
}

/// Light client for verifying remote chain state
pub struct LightClient {
    /// Client states by ID
    client_states: HashMap<ClientId, ClientState>,
    /// Consensus states by (client_id, height)
    consensus_states: HashMap<(ClientId, Height), ConsensusState>,
    /// Next client sequence
    next_client_sequence: u64,
}

impl LightClient {
    /// Create a new light client manager
    pub fn new() -> Self {
        Self {
            client_states: HashMap::new(),
            consensus_states: HashMap::new(),
            next_client_sequence: 0,
        }
    }

    /// Create a new client
    pub fn create_client(
        &mut self,
        client_state: ClientState,
        consensus_state: ConsensusState,
    ) -> IbcResult<ClientId> {
        // Validate client state
        client_state.validate()?;

        // Generate client ID
        let client_id = ClientId::new(client_state.client_type.as_str(), self.next_client_sequence);
        self.next_client_sequence += 1;

        // Store states
        let height = client_state.latest_height;
        self.consensus_states
            .insert((client_id.clone(), height), consensus_state);
        self.client_states.insert(client_id.clone(), client_state);

        Ok(client_id)
    }

    /// Get client state
    pub fn get_client_state(&self, client_id: &ClientId) -> IbcResult<&ClientState> {
        self.client_states
            .get(client_id)
            .ok_or_else(|| IbcError::ClientNotFound(client_id.to_string()))
    }

    /// Get consensus state at height
    pub fn get_consensus_state(
        &self,
        client_id: &ClientId,
        height: Height,
    ) -> IbcResult<&ConsensusState> {
        self.consensus_states
            .get(&(client_id.clone(), height))
            .ok_or_else(|| {
                IbcError::InvalidClientState(format!(
                    "consensus state not found at height {}",
                    height
                ))
            })
    }

    /// Update client with new header
    pub fn update_client(&mut self, client_id: &ClientId, header: Header) -> IbcResult<()> {
        // First check if client exists and is not frozen (immutable borrow)
        {
            let client_state = self
                .client_states
                .get(client_id)
                .ok_or_else(|| IbcError::ClientNotFound(client_id.to_string()))?;

            if client_state.is_frozen() {
                return Err(IbcError::ClientFrozen(client_id.to_string()));
            }
        }

        // Verify header (needs immutable borrow of self)
        self.verify_header(client_id, &header)?;

        // Now get mutable borrow to update client state
        let client_state = self
            .client_states
            .get_mut(client_id)
            .ok_or_else(|| IbcError::ClientNotFound(client_id.to_string()))?;

        // Update client state
        if header.height > client_state.latest_height {
            client_state.latest_height = header.height;
        }

        // Store new consensus state
        let consensus_state = ConsensusState {
            timestamp: header.timestamp,
            root: header.signed_header.app_hash.clone(),
            next_validators_hash: Vec::new(), // Would be extracted from header
        };
        self.consensus_states
            .insert((client_id.clone(), header.height), consensus_state);

        Ok(())
    }

    /// Verify a header against trusted state
    fn verify_header(&self, client_id: &ClientId, header: &Header) -> IbcResult<()> {
        let client_state = self.get_client_state(client_id)?;
        let _trusted_consensus = self.get_consensus_state(client_id, header.trusted_height)?;

        // Check header height is greater than trusted height
        if header.height <= header.trusted_height {
            return Err(IbcError::InvalidClientState(
                "header height must be greater than trusted height".to_string(),
            ));
        }

        // Check timestamp is within max clock drift
        let current_time = Timestamp::now();
        let drift = header
            .timestamp
            .nanoseconds()
            .saturating_sub(current_time.nanoseconds());
        if drift > client_state.max_clock_drift {
            return Err(IbcError::InvalidClientState("clock drift too large".to_string()));
        }

        // Verify signatures (simplified - real implementation would verify BFT signatures)
        self.verify_commit_signatures(client_state, header)?;

        Ok(())
    }

    /// Verify commit signatures
    fn verify_commit_signatures(
        &self,
        client_state: &ClientState,
        header: &Header,
    ) -> IbcResult<()> {
        let commit = &header.signed_header.commit;
        let validator_set = &header.validator_set;

        // Calculate signed voting power
        let mut signed_power = 0u64;
        for sig in &commit.signatures {
            if let Some(validator) = validator_set.get_by_address(&sig.validator_address) {
                // In real implementation, verify signature here
                signed_power += validator.voting_power;
            }
        }

        // Check trust threshold
        if !client_state
            .trust_level
            .check(signed_power, validator_set.total_voting_power)
        {
            return Err(IbcError::ProofVerificationFailed(
                "insufficient voting power".to_string(),
            ));
        }

        Ok(())
    }

    /// Check misbehaviour and freeze client if detected
    pub fn check_misbehaviour(
        &mut self,
        client_id: &ClientId,
        header1: &Header,
        header2: &Header,
    ) -> IbcResult<bool> {
        // Misbehaviour: two different headers at the same height
        if header1.height == header2.height
            && header1.signed_header.block_hash != header2.signed_header.block_hash
        {
            // Freeze the client
            if let Some(state) = self.client_states.get_mut(client_id) {
                state.freeze(header1.height);
            }
            return Ok(true);
        }

        Ok(false)
    }
}

impl Default for LightClient {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn test_client_id() {
        let id = ClientId::new("07-tendermint", 0);
        assert_eq!(id.to_string(), "07-tendermint-0");
        assert_eq!(id.sequence(), Some(0));
    }

    #[test]
    fn test_trust_level() {
        let trust = TrustLevel::two_thirds();
        assert!(trust.check(67, 100)); // 67% >= 66.67%
        assert!(!trust.check(65, 100)); // 65% < 66.67%
    }

    #[test]
    fn test_client_state_validation() {
        let mut state = ClientState::new_synor(
            ChainId::synor_testnet(),
            Height::from_height(100),
        );

        assert!(state.validate().is_ok());

        // Invalid: trusting period >= unbonding period
        state.trusting_period = state.unbonding_period + 1;
        assert!(state.validate().is_err());
    }

    #[test]
    fn test_light_client_create() {
        let mut client = LightClient::new();

        let state = ClientState::new_synor(ChainId::synor_testnet(), Height::from_height(100));

        let consensus = ConsensusState::new(Timestamp::now(), vec![0u8; 32], vec![0u8; 32]);

        let client_id = client.create_client(state, consensus).unwrap();
        assert!(client.get_client_state(&client_id).is_ok());
    }
}
