use crate::addressing::{Address, Addressable};
use crate::database::inner::models;
use crate::util::hash::{b58_decode, hash, Hash, Hashable};
use anyhow::{anyhow, Result};
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::io::{Cursor, Write};
use url::Url;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Entry {
    pub entity: Address,
    pub attribute: String,
    pub value: EntryValue,
    pub provenance: String,
    pub timestamp: NaiveDateTime,
}

#[derive(Debug, Clone)]
pub struct ImmutableEntry(pub Entry);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InvariantEntry {
    pub attribute: String,
    pub value: EntryValue,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "t", content = "c")]
pub enum EntryValue {
    String(String),
    Number(f64),
    Address(Address),
    Null,
    Invalid,
}

impl TryFrom<&models::Entry> for Entry {
    type Error = anyhow::Error;

    fn try_from(e: &models::Entry) -> Result<Self, Self::Error> {
        if let Some(value_str) = &e.value_str {
            Ok(Entry {
                entity: Address::decode(&e.entity)?,
                attribute: e.attribute.clone(),
                value: value_str.parse()?,
                provenance: e.provenance.clone(),
                timestamp: e.timestamp,
            })
        } else if let Some(value_num) = e.value_num {
            Ok(Entry {
                entity: Address::decode(&e.entity)?,
                attribute: e.attribute.clone(),
                value: EntryValue::Number(value_num),
                provenance: e.provenance.clone(),
                timestamp: e.timestamp,
            })
        } else {
            Ok(Entry {
                entity: Address::decode(&e.entity)?,
                attribute: e.attribute.clone(),
                value: EntryValue::Number(f64::NAN),
                provenance: e.provenance.clone(),
                timestamp: e.timestamp,
            })
        }
    }
}

impl TryFrom<&Entry> for models::Entry {
    type Error = anyhow::Error;

    fn try_from(e: &Entry) -> Result<Self, Self::Error> {
        if e.attribute.is_empty() {
            return Err(anyhow!("Attribute cannot be empty."));
        }
        let base_entry = models::Entry {
            identity: e.address()?.encode()?,
            entity_searchable: match &e.entity {
                Address::Attribute(attr) => Some(attr.clone()),
                Address::Url(url) => Some(url.to_string()),
                _ => None,
            },
            entity: e.entity.encode()?,
            attribute: e.attribute.clone(),
            value_str: None,
            value_num: None,
            immutable: false,
            provenance: e.provenance.clone(),
            timestamp: e.timestamp,
        };

        match e.value {
            EntryValue::Number(n) => Ok(models::Entry {
                value_str: None,
                value_num: Some(n),
                ..base_entry
            }),
            _ => Ok(models::Entry {
                value_str: Some(e.value.to_string()?),
                value_num: None,
                ..base_entry
            }),
        }
    }
}

impl TryFrom<&ImmutableEntry> for models::Entry {
    type Error = anyhow::Error;

    fn try_from(e: &ImmutableEntry) -> Result<Self, Self::Error> {
        Ok(models::Entry {
            immutable: true,
            ..models::Entry::try_from(&e.0)?
        })
    }
}

impl TryFrom<&InvariantEntry> for Entry {
    type Error = anyhow::Error;

    fn try_from(invariant: &InvariantEntry) -> Result<Self, Self::Error> {
        Ok(Entry {
            entity: invariant.entity()?,
            attribute: invariant.attribute.clone(),
            value: invariant.value.clone(),
            provenance: "INVARIANT".to_string(),
            timestamp: NaiveDateTime::from_timestamp_opt(0, 0).unwrap(),
        })
    }
}

impl InvariantEntry {
    pub fn entity(&self) -> Result<Address> {
        let mut entity = Cursor::new(vec![0u8; 0]);
        entity.write_all(self.attribute.as_bytes())?;
        entity.write_all(self.value.to_string()?.as_bytes())?;
        Ok(Address::Hash(hash(entity.into_inner())))
    }
}

impl std::fmt::Display for Entry {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{} | {} | {}", self.entity, self.attribute, self.value)
    }
}

impl Hashable for Entry {
    fn hash(self: &Entry) -> Result<Hash> {
        let mut result = Cursor::new(vec![0u8; 0]);
        result.write_all(self.entity.encode()?.as_slice())?;
        result.write_all(self.attribute.as_bytes())?;
        result.write_all(self.value.to_string()?.as_bytes())?;
        Ok(hash(result.get_ref()))
    }
}

impl Hashable for InvariantEntry {
    fn hash(&self) -> Result<Hash> {
        Entry::try_from(self)?.hash()
    }
}

impl Addressable for Entry {}
impl Addressable for InvariantEntry {}

impl EntryValue {
    pub fn to_string(&self) -> Result<String> {
        let (type_char, content) = match self {
            EntryValue::String(value) => ('S', value.to_owned()),
            EntryValue::Number(n) => ('N', n.to_string()),
            EntryValue::Address(address) => ('O', address.to_string()),
            EntryValue::Null => ('X', "".to_string()),
            EntryValue::Invalid => return Err(anyhow!("Cannot serialize invalid value.")),
        };

        Ok(format!("{}{}", type_char, content))
    }

    pub fn guess_from<S: AsRef<str>>(string: S) -> Self {
        let string = string.as_ref();
        match string.parse::<f64>() {
            Ok(num) => EntryValue::Number(num),
            Err(_) => {
                if let Ok(url) = Url::parse(string) {
                    EntryValue::Address(Address::Url(url))
                } else {
                    EntryValue::String(string.to_string())
                }
            }
        }
    }
}

impl std::str::FromStr for EntryValue {
    type Err = std::convert::Infallible;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.len() < 2 {
            match s.chars().next() {
                Some('S') => Ok(EntryValue::String("".into())),
                Some('X') => Ok(EntryValue::Null),
                _ => Ok(EntryValue::Invalid),
            }
        } else {
            let (type_char, content) = s.split_at(1);
            match (type_char, content) {
                ("S", content) => Ok(EntryValue::String(String::from(content))),
                ("N", content) => {
                    if let Ok(n) = content.parse::<f64>() {
                        Ok(EntryValue::Number(n))
                    } else {
                        Ok(EntryValue::Invalid)
                    }
                }
                ("O", content) => {
                    if let Ok(addr) = b58_decode(content).and_then(|v| Address::decode(&v)) {
                        Ok(EntryValue::Address(addr))
                    } else {
                        Ok(EntryValue::Invalid)
                    }
                }
                _ => Ok(EntryValue::Invalid),
            }
        }
    }
}

impl From<Url> for EntryValue {
    fn from(value: Url) -> Self {
        EntryValue::Address(Address::Url(value))
    }
}

impl std::fmt::Display for EntryValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let (entry_type, entry_value) = match self {
            EntryValue::Address(address) => ("ADDRESS", address.to_string()),
            EntryValue::String(string) => ("STRING", string.to_owned()),
            EntryValue::Number(n) => ("NUMBER", n.to_string()),
            EntryValue::Null => ("NULL", "NULL".to_string()),
            EntryValue::Invalid => ("INVALID", "INVALID".to_string()),
        };
        write!(f, "{}: {}", entry_type, entry_value)
    }
}

impl From<&str> for EntryValue {
    fn from(str: &str) -> Self {
        Self::String(str.to_string())
    }
}

impl From<String> for EntryValue {
    fn from(str: String) -> Self {
        Self::String(str)
    }
}

impl From<f64> for EntryValue {
    fn from(num: f64) -> Self {
        Self::Number(num)
    }
}

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

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

    #[test]
    fn test_value_from_to_string() -> Result<()> {
        let entry = EntryValue::String("hello".to_string());
        let encoded = entry.to_string()?;
        let decoded = encoded.parse::<EntryValue>()?;
        assert_eq!(entry, decoded);

        let entry = EntryValue::Number(1337.93);
        let encoded = entry.to_string()?;
        let decoded = encoded.parse::<EntryValue>()?;
        assert_eq!(entry, decoded);

        let entry = EntryValue::Address(Address::Url(Url::parse("https://upend.dev").unwrap()));
        let encoded = entry.to_string()?;
        let decoded = encoded.parse::<EntryValue>()?;
        assert_eq!(entry, decoded);

        let entry = EntryValue::String("".to_string());
        let encoded = entry.to_string()?;
        let decoded = encoded.parse::<EntryValue>()?;
        assert_eq!(entry, decoded);

        let entry = EntryValue::Null;
        let encoded = entry.to_string()?;
        let decoded = encoded.parse::<EntryValue>()?;
        assert_eq!(entry, decoded);

        Ok(())
    }

    #[test]
    fn test_into() {
        assert_eq!(EntryValue::String(String::from("UPEND")), "UPEND".into());
        assert_eq!(EntryValue::Number(1337.93), 1337.93.into());
        let addr = Address::Url(Url::parse("https://upend.dev").unwrap());
        assert_eq!(EntryValue::Address(addr.clone()), addr.into());
    }

    #[test]
    fn test_guess_value() {
        assert_eq!(
            EntryValue::guess_from("UPEND"),
            EntryValue::String("UPEND".into())
        );
        assert_eq!(
            EntryValue::guess_from("1337.93"),
            EntryValue::Number(1337.93)
        );
        assert_eq!(
            EntryValue::guess_from("https://upend.dev"),
            EntryValue::Address(Address::Url(Url::parse("https://upend.dev").unwrap()))
        );
    }
}
