use std::{cmp::max, fmt};

use zebra_chain::{
    block,
    parameters::{
        Network::{self, *},
        NetworkUpgrade::{self, *},
    },
};

use crate::constants::{self, magics, CURRENT_NETWORK_PROTOCOL_VERSION};

#[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary;

/// A magic number identifying the network.
#[derive(Copy, Clone, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
pub struct Magic(pub [u8; 4]);

impl fmt::Debug for Magic {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_tuple("Magic").field(&hex::encode(self.0)).finish()
    }
}
pub(crate) trait ParameterMagic {
    fn magic_value(&self) -> Magic;
}
impl ParameterMagic for Network {
    /// Get the magic value associated to this `Network`.
    fn magic_value(&self) -> Magic {
        match self {
            Network::Mainnet => magics::MAINNET,
            // TODO: Move `Magic` struct definition to `zebra-chain`, add it as a field in `testnet::Parameters`, and return it here.
            Network::Testnet(_params) => magics::TESTNET,
        }
    }
}

/// A protocol version number.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct Version(pub u32);

impl fmt::Display for Version {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&self.0.to_string())
    }
}

impl Version {
    /// Returns the minimum remote node network protocol version for `network` and
    /// `height`. Zebra disconnects from peers with lower versions.
    ///
    /// # Panics
    ///
    /// If we are incompatible with our own minimum remote protocol version.
    pub fn min_remote_for_height(
        network: &Network,
        height: impl Into<Option<block::Height>>,
    ) -> Version {
        let height = height.into().unwrap_or(block::Height(0));
        let min_spec = Version::min_specified_for_height(network, height);

        // shut down if our own version is too old
        assert!(
            constants::CURRENT_NETWORK_PROTOCOL_VERSION >= min_spec,
            "Zebra does not implement the minimum specified {:?} protocol version for {:?} at {:?}",
            NetworkUpgrade::current(network, height),
            network,
            height,
        );

        max(min_spec, Version::initial_min_for_network(network))
    }

    /// Returns the minimum supported network protocol version for `network`.
    ///
    /// This is the minimum peer version when Zebra is significantly behind current tip:
    /// - during the initial block download,
    /// - after Zebra restarts, and
    /// - after Zebra's local network is slow or shut down.
    fn initial_min_for_network(network: &Network) -> Version {
        *constants::INITIAL_MIN_NETWORK_PROTOCOL_VERSION
            .get(&network.kind())
            .expect("We always have a value for testnet or mainnet")
    }

    /// Returns the minimum specified network protocol version for `network` and
    /// `height`.
    ///
    /// This is the minimum peer version when Zebra is close to the current tip.
    fn min_specified_for_height(network: &Network, height: block::Height) -> Version {
        let network_upgrade = NetworkUpgrade::current(network, height);
        Version::min_specified_for_upgrade(network, network_upgrade)
    }

    /// Returns the minimum specified network protocol version for `network` and
    /// `network_upgrade`.
    pub(crate) fn min_specified_for_upgrade(
        network: &Network,
        network_upgrade: NetworkUpgrade,
    ) -> Version {
        // TODO: Should we reject earlier protocol versions during our initial
        //       sync? zcashd accepts 170_002 or later during its initial sync.
        Version(match (network, network_upgrade) {
            (_, Genesis) | (_, BeforeOverwinter) => 170_002,
            (Testnet(params), Overwinter) if params.is_default_testnet() => 170_003,
            (Mainnet, Overwinter) => 170_005,
            // TODO: Use 170_006 for (Testnet(params), Sapling) if params.is_regtest() (`Regtest` in zcashd uses
            //       version 170_006 for Sapling, and the same values as Testnet for other network upgrades.)
            (_, Sapling) => 170_007,
            (Testnet(params), Blossom) if params.is_default_testnet() => 170_008,
            (Mainnet, Blossom) => 170_009,
            (Testnet(params), Heartwood) if params.is_default_testnet() => 170_010,
            (Mainnet, Heartwood) => 170_011,
            (Testnet(params), Canopy) if params.is_default_testnet() => 170_012,
            (Mainnet, Canopy) => 170_013,
            (Testnet(params), Nu5) if params.is_default_testnet() => 170_050,
            (Mainnet, Nu5) => 170_100,

            // It should be fine to reject peers with earlier network protocol versions on custom testnets for now.
            (Testnet(_params), _) => CURRENT_NETWORK_PROTOCOL_VERSION.0,
        })
    }
}

bitflags! {
    /// A bitflag describing services advertised by a node in the network.
    ///
    /// Note that bits 24-31 are reserved for temporary experiments; other
    /// service bits should be allocated via the ZIP process.
    #[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
    pub struct PeerServices: u64 {
        /// NODE_NETWORK means that the node is a full node capable of serving
        /// blocks, as opposed to a light client that makes network requests but
        /// does not provide network services.
        const NODE_NETWORK = 1;
    }
}

/// A nonce used in the networking layer to identify messages.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
pub struct Nonce(pub u64);

impl Default for Nonce {
    fn default() -> Self {
        use rand::{thread_rng, Rng};
        Self(thread_rng().gen())
    }
}

/// A random value to add to the seed value in a hash function.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
pub struct Tweak(pub u32);

impl Default for Tweak {
    fn default() -> Self {
        use rand::{thread_rng, Rng};
        Self(thread_rng().gen())
    }
}

/// A Bloom filter consisting of a bit field of arbitrary byte-aligned
/// size, maximum size is 36,000 bytes.
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
pub struct Filter(pub Vec<u8>);

#[cfg(test)]
mod proptest {

    use proptest::prelude::*;

    use super::Magic;

    use crate::constants::magics;

    #[test]
    fn magic_debug() {
        let _init_guard = zebra_test::init();

        assert_eq!(format!("{:?}", magics::MAINNET), "Magic(\"24e92764\")");
        assert_eq!(format!("{:?}", magics::TESTNET), "Magic(\"fa1af9bf\")");
    }

    proptest! {

        #[test]
        fn proptest_magic_from_array(data in any::<[u8; 4]>()) {
            let _init_guard = zebra_test::init();

            assert_eq!(format!("{:?}", Magic(data)), format!("Magic({:x?})", hex::encode(data)));
        }
    }
}

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

    #[test]
    fn version_extremes_mainnet() {
        version_extremes(&Mainnet)
    }

    #[test]
    fn version_extremes_testnet() {
        version_extremes(&Network::new_default_testnet())
    }

    /// Test the min_specified_for_upgrade and min_specified_for_height functions for `network` with
    /// extreme values.
    fn version_extremes(network: &Network) {
        let _init_guard = zebra_test::init();

        assert_eq!(
            Version::min_specified_for_height(network, block::Height(0)),
            Version::min_specified_for_upgrade(network, BeforeOverwinter),
        );

        // We assume that the last version we know about continues forever
        // (even if we suspect that won't be true)
        assert_ne!(
            Version::min_specified_for_height(network, block::Height::MAX),
            Version::min_specified_for_upgrade(network, BeforeOverwinter),
        );
    }

    #[test]
    fn version_consistent_mainnet() {
        version_consistent(&Mainnet)
    }

    #[test]
    fn version_consistent_testnet() {
        version_consistent(&Network::new_default_testnet())
    }

    /// Check that the min_specified_for_upgrade and min_specified_for_height functions
    /// are consistent for `network`.
    fn version_consistent(network: &Network) {
        let _init_guard = zebra_test::init();

        let highest_network_upgrade = NetworkUpgrade::current(network, block::Height::MAX);
        assert!(highest_network_upgrade == Nu5 || highest_network_upgrade == Canopy,
                "expected coverage of all network upgrades: add the new network upgrade to the list in this test");

        for &network_upgrade in &[
            BeforeOverwinter,
            Overwinter,
            Sapling,
            Blossom,
            Heartwood,
            Canopy,
            Nu5,
        ] {
            let height = network_upgrade.activation_height(network);
            if let Some(height) = height {
                assert_eq!(
                    Version::min_specified_for_upgrade(network, network_upgrade),
                    Version::min_specified_for_height(network, height)
                );
            }
        }
    }
}
