use core::fmt;
use std::ops::Deref;

use casper_types::Key;
use odra_core::prelude::Address as _Address;
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use web_sys::HtmlInputElement;

use crate::types::public_key::PublicKey;

#[derive(Debug, Clone, Deserialize, Serialize, Copy, PartialEq, Eq)]
#[wasm_bindgen(inspectable)]
pub struct Address(_Address);

#[wasm_bindgen]
impl Address {
    #[wasm_bindgen(constructor)]
    pub fn new(address: &str) -> Result<Self, JsError> {
        use std::str::FromStr;

        _Address::from_str(address).map(Address).map_err(|err| {
            JsError::new(&format!(
                "Could not create Address from string {address}: {err:?}"
            ))
        })
    }

    #[wasm_bindgen(js_name = "fromHtmlInput")]
    pub fn from_input(input: HtmlInputElement) -> Result<Self, JsError> {
        let value = input.value();
        Self::new(value.trim())
    }

    #[wasm_bindgen(js_name = "fromPublicKey")]
    pub fn from_public_key(input: &str) -> Result<Self, JsError> {
        PublicKey::new(input).map(Address::from).map_err(|err| {
            JsError::new(&format!(
                "Could not create Address from PublicKey string {input}: {err:?}"
            ))
        })
    }

    #[wasm_bindgen(getter)]
    pub fn value(&self) -> String {
        self.to_formatted_string()
    }
}

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

impl Deref for Address {
    type Target = _Address;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl From<Address> for _Address {
    fn from(address: Address) -> Self {
        address.0
    }
}

impl From<PublicKey> for Address {
    fn from(value: PublicKey) -> Self {
        let pk: casper_types::PublicKey = value.into();
        Address(_Address::from(pk))
    }
}

impl From<_Address> for Address {
    fn from(address: _Address) -> Self {
        Address(address)
    }
}

impl TryFrom<Key> for Address {
    type Error = JsError;

    fn try_from(key: Key) -> Result<Self, Self::Error> {
        Ok(Address(_Address::try_from(key).map_err(|_| {
            JsError::new("Address could not be created from Key")
        })?))
    }
}
