#![deny(clippy::arithmetic_side_effects)]
#![deny(clippy::cast_possible_truncation)]

use clap::ValueEnum;
use fuel_core_types::{
    fuel_crypto::{
        SecretKey,
        rand::{
            SeedableRng,
            prelude::StdRng,
        },
    },
    fuel_tx::Input,
    fuel_types::Address,
};
use libp2p_identity::{
    Keypair,
    PeerId,
    secp256k1,
};
use serde::Serialize;
use std::{
    ops::Deref,
    str::FromStr,
};

#[derive(Clone, Copy, Debug, Default, Serialize, ValueEnum)]
#[serde(rename_all = "kebab-case")]
pub enum KeyType {
    #[default]
    BlockProduction,
    Peering,
}

impl From<KeyType> for &'static str {
    fn from(key_type: KeyType) -> Self {
        match key_type {
            KeyType::BlockProduction => "block-production",
            KeyType::Peering => "p2p",
        }
    }
}

#[derive(Clone, Debug, Serialize)]
pub struct ParseSecretResponse {
    #[serde(skip_serializing_if = "Option::is_none")]
    address: Option<Address>,
    #[serde(
        serialize_with = "serialize_option_to_string",
        skip_serializing_if = "Option::is_none"
    )]
    peer_id: Option<PeerId>,
    #[serde(rename = "type")]
    typ: KeyType,
}

#[derive(Clone, Debug, Serialize)]
pub struct NewKeyResponse {
    secret: SecretKey,
    #[serde(skip_serializing_if = "Option::is_none")]
    address: Option<Address>,
    #[serde(
        serialize_with = "serialize_option_to_string",
        skip_serializing_if = "Option::is_none"
    )]
    peer_id: Option<PeerId>,
    #[serde(rename = "type")]
    typ: KeyType,
}

fn serialize_option_to_string<S, T>(
    opt: &Option<T>,
    serializer: S,
) -> Result<S::Ok, S::Error>
where
    S: serde::Serializer,
    T: ToString,
{
    if let Some(value) = opt.as_ref() {
        value.to_string().serialize(serializer)
    } else {
        serializer.serialize_none()
    }
}

pub fn new_key(key_type: KeyType) -> anyhow::Result<NewKeyResponse> {
    let mut rng = StdRng::from_entropy();
    let secret = SecretKey::random(&mut rng);
    let public_key = secret.public_key();

    Ok(match key_type {
        KeyType::BlockProduction => {
            let address = Input::owner(&public_key);
            NewKeyResponse {
                secret,
                address: Some(address),
                peer_id: None,
                typ: key_type,
            }
        }
        KeyType::Peering => {
            let mut bytes = *secret.deref();
            let p2p_secret = secp256k1::SecretKey::try_from_bytes(&mut bytes)
                .expect("Should be a valid private key");
            let p2p_keypair = secp256k1::Keypair::from(p2p_secret);
            let libp2p_keypair = Keypair::from(p2p_keypair);
            let peer_id = PeerId::from_public_key(&libp2p_keypair.public());
            NewKeyResponse {
                secret,
                address: None,
                peer_id: Some(peer_id),
                typ: key_type,
            }
        }
    })
}

pub fn parse_secret(
    key_type: KeyType,
    secret: &str,
) -> anyhow::Result<ParseSecretResponse> {
    let secret =
        SecretKey::from_str(secret).map_err(|_| anyhow::anyhow!("invalid secret key"))?;
    Ok(match key_type {
        KeyType::BlockProduction => {
            let address = Input::owner(&secret.public_key());
            ParseSecretResponse {
                address: Some(address),
                peer_id: None,
                typ: key_type,
            }
        }
        KeyType::Peering => {
            let mut bytes = *secret.deref();
            let p2p_secret = secp256k1::SecretKey::try_from_bytes(&mut bytes)
                .expect("Should be a valid private key");
            let p2p_keypair = secp256k1::Keypair::from(p2p_secret);
            let libp2p_keypair = Keypair::from(p2p_keypair);
            let peer_id = PeerId::from_public_key(&libp2p_keypair.public());
            ParseSecretResponse {
                address: None,
                peer_id: Some(peer_id),
                typ: key_type,
            }
        }
    })
}
