use std::{fmt, str::FromStr};

use anyhow::anyhow;

#[derive(Debug, Clone, Copy)]
pub enum EVMNetworkName {
    Mainnet,
    Goerli,
    Sepolia,
    Holesky,
    Hoodi,
    Gnosis,
    Chiado,
}

#[derive(Debug, Clone, Copy)]
pub enum NetworkName {
    Preset(EVMNetworkName),
    Devnet,
}

#[derive(Debug, Clone, Copy)]
pub struct Network {
    pub name: NetworkName,
    pub chain_id: u64,
    pub dencun_fork_slot: u32,
    pub epoch: u32,
}

impl Network {
    /// Construct a `Network` from a known network with baked-in params.
    pub fn new(name: EVMNetworkName) -> Self {
        match name {
            EVMNetworkName::Mainnet => Network {
                name: name.into(),
                chain_id: 1,
                dencun_fork_slot: 8_626_176, // epoch 269_568
                epoch: 269_568,
            },
            EVMNetworkName::Goerli => Network {
                name: name.into(),
                chain_id: 5,
                dencun_fork_slot: 7_413_760, // epoch 231_680
                epoch: 231_680,
            },
            EVMNetworkName::Sepolia => Network {
                name: name.into(),
                chain_id: 11155111,
                dencun_fork_slot: 4_243_456, // epoch 132_608
                epoch: 132_608,
            },
            EVMNetworkName::Holesky => Network {
                name: name.into(),
                chain_id: 17000,
                dencun_fork_slot: 950_272, // epoch 29_696
                epoch: 29_696,
            },
            EVMNetworkName::Hoodi => Network {
                name: name.into(),
                chain_id: 560_048,
                dencun_fork_slot: 0,
                epoch: 0,
            },
            EVMNetworkName::Gnosis => Network {
                name: name.into(),
                chain_id: 100,
                dencun_fork_slot: 14_237_696, // epoch 889_856
                epoch: 889_856,
            },
            EVMNetworkName::Chiado => Network {
                name: name.into(),
                chain_id: 10_200,
                dencun_fork_slot: 8_265_728, // epoch 516_608
                epoch: 516_608,
            },
        }
    }

    /// Construct a custom devnet with your own parameters.
    pub fn new_devnet(chain_id: u64, dencun_fork_slot: u32, epoch: u32) -> Self {
        Network {
            name: NetworkName::Devnet,
            chain_id,
            dencun_fork_slot,
            epoch,
        }
    }
}

impl From<EVMNetworkName> for NetworkName {
    fn from(value: EVMNetworkName) -> Self {
        NetworkName::Preset(value)
    }
}

impl fmt::Display for EVMNetworkName {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            EVMNetworkName::Mainnet => "mainnet",
            EVMNetworkName::Goerli => "goerli",
            EVMNetworkName::Sepolia => "sepolia",
            EVMNetworkName::Holesky => "holesky",
            EVMNetworkName::Hoodi => "hoodi",
            EVMNetworkName::Gnosis => "gnosis",
            EVMNetworkName::Chiado => "chiado",
        };
        write!(f, "{}", s)
    }
}

impl fmt::Display for NetworkName {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            NetworkName::Preset(net) => net.fmt(f),
            NetworkName::Devnet => write!(f, "devnet"),
        }
    }
}

impl FromStr for NetworkName {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, anyhow::Error> {
        let network = match s.to_lowercase().as_str() {
            "mainnet" => EVMNetworkName::Mainnet.into(),
            "goerli" => EVMNetworkName::Goerli.into(),
            "gnosis" => EVMNetworkName::Gnosis.into(),
            "sepolia" => EVMNetworkName::Sepolia.into(),
            "holesky" => EVMNetworkName::Holesky.into(),
            "hoodi" => EVMNetworkName::Hoodi.into(),
            "chiado" => EVMNetworkName::Chiado.into(),
            "devnet" => NetworkName::Devnet,
            unknown_network => {
                return Err(anyhow!("{unknown_network} not supported"));
            }
        };

        Ok(network)
    }
}
