use core::fmt;
use std::{collections::HashMap, ops::Deref, str::FromStr};

use pallas_addresses::Address as PallasAddress;
use serde::{
    de::{self, Visitor},
    ser::SerializeMap,
    Deserialize, Deserializer, Serialize, Serializer,
};

use super::{
    model::{Address, Input, MintAssets, OutputAssets, RedeemerPurpose},
    Bytes, Bytes32, Bytes64, Hash28,
};

impl Serialize for Bytes32 {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&hex::encode(self.0))
    }
}

impl<'de> Deserialize<'de> for Bytes32 {
    fn deserialize<D>(deserializer: D) -> Result<Bytes32, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_str(Bytes32Visitor)
    }
}

struct Bytes32Visitor;

impl<'de> Visitor<'de> for Bytes32Visitor {
    type Value = Bytes32;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("32 bytes hex encoded")
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(Bytes32(
            hex::decode(v)
                .map_err(|_| E::custom("invalid hex"))?
                .try_into()
                .map_err(|_| E::custom("invalid length"))?,
        ))
    }
}

impl Serialize for Hash28 {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&hex::encode(self.0))
    }
}

impl<'de> Deserialize<'de> for Hash28 {
    fn deserialize<D>(deserializer: D) -> Result<Hash28, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_str(Hash28Visitor)
    }
}

struct Hash28Visitor;

impl<'de> Visitor<'de> for Hash28Visitor {
    type Value = Hash28;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("28 bytes hex encoded")
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(Hash28(
            hex::decode(v)
                .map_err(|_| E::custom("invalid hex"))?
                .try_into()
                .map_err(|_| E::custom("invalid length"))?,
        ))
    }
}

impl Serialize for Bytes {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&hex::encode(&self.0))
    }
}

impl<'de> Deserialize<'de> for Bytes {
    fn deserialize<D>(deserializer: D) -> Result<Bytes, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_str(BytesVisitor)
    }
}

struct BytesVisitor;

impl<'de> Visitor<'de> for BytesVisitor {
    type Value = Bytes;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("bytes hex encoded")
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(Bytes(hex::decode(v).map_err(|_| E::custom("invalid hex"))?))
    }
}

impl Serialize for OutputAssets {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(self.deref().len()))?;

        for (policy, assets) in self.deref().iter() {
            let mut assets_map: HashMap<String, u64> = HashMap::new();

            for (asset, amount) in assets {
                assets_map.insert(hex::encode(&asset.0), *amount);
            }

            map.serialize_entry(policy, &assets_map)?;
        }

        map.end()
    }
}

impl<'de> Deserialize<'de> for OutputAssets {
    fn deserialize<D>(deserializer: D) -> Result<OutputAssets, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_map(OutputAssetsVisitor)
    }
}

struct OutputAssetsVisitor;

impl<'de> Visitor<'de> for OutputAssetsVisitor {
    type Value = OutputAssets;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str(
            "map of hex encoded policy ids to map of hex encoded asset names to u64 amounts",
        )
    }

    fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
    where
        A: de::MapAccess<'de>,
    {
        let mut out_map = HashMap::new();

        while let Some((key, value)) = access.next_entry()? {
            out_map.insert(key, value);
        }

        Ok(OutputAssets::from_map(out_map))
    }
}

impl Serialize for MintAssets {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(self.deref().len()))?;

        for (policy, assets) in self.deref().iter() {
            let mut assets_map: HashMap<String, i64> = HashMap::new();

            for (asset, amount) in assets {
                assets_map.insert(hex::encode(&asset.0), *amount);
            }

            map.serialize_entry(policy, &assets_map)?;
        }

        map.end()
    }
}

impl<'de> Deserialize<'de> for MintAssets {
    fn deserialize<D>(deserializer: D) -> Result<MintAssets, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_map(MintAssetsVisitor)
    }
}

struct MintAssetsVisitor;

impl<'de> Visitor<'de> for MintAssetsVisitor {
    type Value = MintAssets;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str(
            "map of hex encoded policy ids to map of hex encoded asset names to u64 amounts",
        )
    }

    fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
    where
        A: de::MapAccess<'de>,
    {
        let mut out_map = HashMap::new();

        while let Some((key, value)) = access.next_entry()? {
            out_map.insert(key, value);
        }

        Ok(MintAssets::from_map(out_map))
    }
}

impl Serialize for RedeemerPurpose {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let str = match self {
            RedeemerPurpose::Spend(Input { tx_hash, txo_index }) => {
                format!("spend:{}#{}", hex::encode(tx_hash.0), txo_index)
            }
            RedeemerPurpose::Mint(hash) => format!("mint:{}", hex::encode(hash.0)),
        };

        serializer.serialize_str(&str)
    }
}

impl<'de> Deserialize<'de> for RedeemerPurpose {
    fn deserialize<D>(deserializer: D) -> Result<RedeemerPurpose, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_str(RedeemerPurposeVisitor)
    }
}

struct RedeemerPurposeVisitor;

impl<'de> Visitor<'de> for RedeemerPurposeVisitor {
    type Value = RedeemerPurpose;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("'spend:{hex_txid}#{index}' or 'mint:{hex_policyid}'")
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        let (tag, item) = v
            .split_once(':')
            .ok_or(E::custom("invalid redeemer purpose"))?;

        match tag {
            "spend" => {
                let (hash, index) = item
                    .split_once('#')
                    .ok_or(E::custom("invalid spend redeemer item"))?;

                let tx_hash = Bytes32(
                    hex::decode(hash)
                        .map_err(|_| E::custom("invalid spend redeemer item txid hex"))?
                        .try_into()
                        .map_err(|_| E::custom("invalid spend redeemer txid len"))?,
                );
                let txo_index = index
                    .parse()
                    .map_err(|_| E::custom("invalid spend redeemer item index"))?;

                Ok(RedeemerPurpose::Spend(Input { tx_hash, txo_index }))
            }
            "mint" => {
                let hash = Hash28(
                    hex::decode(item)
                        .map_err(|_| E::custom("invalid mint redeemer item policy hex"))?
                        .try_into()
                        .map_err(|_| E::custom("invalid mint redeemer policy len"))?,
                );

                Ok(RedeemerPurpose::Mint(hash))
            }
            _ => Err(E::custom("invalid redeemer tag")),
        }
    }
}

impl Serialize for Address {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.0.to_string())
    }
}

impl<'de> Deserialize<'de> for Address {
    fn deserialize<D>(deserializer: D) -> Result<Address, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_str(AddressVisitor)
    }
}

struct AddressVisitor;

impl<'de> Visitor<'de> for AddressVisitor {
    type Value = Address;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("bech32 shelley address or base58 byron address")
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(Address(
            PallasAddress::from_str(v).map_err(|_| E::custom("invalid address"))?,
        ))
    }
}

impl Serialize for Bytes64 {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&hex::encode(self.0))
    }
}

impl<'de> Deserialize<'de> for Bytes64 {
    fn deserialize<D>(deserializer: D) -> Result<Bytes64, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_str(Bytes64Visitor)
    }
}

struct Bytes64Visitor;

impl<'de> Visitor<'de> for Bytes64Visitor {
    type Value = Bytes64;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("64 bytes hex encoded")
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(Bytes64(
            hex::decode(v)
                .map_err(|_| E::custom("invalid hex"))?
                .try_into()
                .map_err(|_| E::custom("invalid length"))?,
        ))
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use pallas_addresses::Address as PallasAddress;
    use pallas_primitives::{babbage::PlutusData, Fragment};

    use crate::transaction::{model::*, Bytes64, DatumBytes, DatumHash, Hash28, TransactionStatus};

    use super::*;

    #[test]
    fn staging_json_roundtrip() {
        let mut datums: HashMap<DatumHash, DatumBytes> = HashMap::new();
        datums.insert(Bytes32([0; 32]), Bytes([0; 100].to_vec()));

        let tx = StagingTransaction {
            version: String::from("v1"),
            status: TransactionStatus::Staging,
            inputs: Some(
                vec![
                    Input {
                        tx_hash: Bytes32([0; 32]),
                        txo_index: 1
                    }
                ]
            ) ,
            reference_inputs: Some(vec![
                Input {
                    tx_hash: Bytes32([1; 32]),
                    txo_index: 0
                }
            ]),
            outputs: Some(vec![
                Output {
                    address: Address(PallasAddress::from_str("addr1g9ekml92qyvzrjmawxkh64r2w5xr6mg9ngfmxh2khsmdrcudevsft64mf887333adamant").unwrap()),
                    lovelace: 1337,
                    assets: Some(
                        OutputAssets::from_map(
                            vec![
                                (
                                    Hash28([0; 28]),
                                    (vec![(Bytes(vec![0]), 1337)]).into_iter().collect::<HashMap<_, _>>()
                                )
                            ].into_iter().collect::<HashMap<_, _>>()
                        )
                    ),
                    datum: Some(Datum { kind: DatumKind::Hash, bytes: Bytes([0; 32].to_vec()) }),
                    script: Some(Script { kind: ScriptKind::Native, bytes: Bytes([1; 100].to_vec()) }),
                }
            ]),
            fee: Some(1337),
            mint: Some(
                MintAssets::from_map(
                    vec![
                        (
                            Hash28([0; 28]),
                            (vec![(Bytes(vec![0]), -1337)]).into_iter().collect::<HashMap<_, _>>()
                        )
                    ].into_iter().collect::<HashMap<_, _>>()
                )
            ),
            valid_from_slot: Some(1337),
            invalid_from_slot: Some(1337),
            network_id: Some(1),
            collateral_inputs: Some(vec![
                Input {
                    tx_hash: Bytes32([2; 32]),
                    txo_index: 0
                }
            ]),
            collateral_output: Some(Output { address: Address(PallasAddress::from_str("addr1g9ekml92qyvzrjmawxkh64r2w5xr6mg9ngfmxh2khsmdrcudevsft64mf887333adamant").unwrap()), lovelace: 1337, assets: None, datum: None, script: None }),
            disclosed_signers: Some(vec![Hash28([0; 28])]),
            scripts: Some(
                vec![
                    (
                        Hash28([0; 28]),
                        Script { kind: ScriptKind::PlutusV1, bytes: Bytes([0; 100].to_vec()) }
                    )
                ].into_iter().collect::<HashMap<_, _>>()
            ),
            datums: Some(datums),
            redeemers: Some(Redeemers::from_map(vec![
                (RedeemerPurpose::Spend(Input { tx_hash: Bytes32([4; 32]), txo_index: 1 }), (Bytes(PlutusData::Array(vec![]).encode_fragment().unwrap()), Some(ExUnits { mem: 1337, steps: 7331 }))),
                (RedeemerPurpose::Mint(Hash28([5; 28])), (Bytes(PlutusData::Array(vec![]).encode_fragment().unwrap()), None)),
            ].into_iter().collect::<HashMap<_, _>>())),
            signature_amount_override: Some(5),
            change_address: Some(Address(PallasAddress::from_str("addr1g9ekml92qyvzrjmawxkh64r2w5xr6mg9ngfmxh2khsmdrcudevsft64mf887333adamant").unwrap())),
            script_data_hash: Some(Bytes32([0; 32])),
        };

        let serialised_tx = serde_json::to_string(&tx).unwrap();
        dbg!(&serialised_tx);

        let deserialised_tx: StagingTransaction = serde_json::from_str(&serialised_tx).unwrap();

        assert_eq!(tx, deserialised_tx)
    }

    #[test]
    fn built_json_roundtrip() {
        let tx = BuiltTransaction {
            version: "3".into(),
            status: TransactionStatus::Built,
            era: BuilderEra::Babbage,
            tx_hash: Bytes32([0; 32]),
            tx_bytes: Bytes([6; 100].to_vec()),
            signatures: Some(
                vec![(Bytes32([20; 32]), Bytes64([9; 64]))]
                    .into_iter()
                    .collect::<HashMap<_, _>>(),
            ),
        };

        let serialised_tx = serde_json::to_string(&tx).unwrap();

        let deserialised_tx: BuiltTransaction = serde_json::from_str(&serialised_tx).unwrap();

        assert_eq!(tx, deserialised_tx)
    }
}
