use std::collections::BTreeMap;
use std::str::FromStr;

use chrono::{DateTime, NaiveDate, Utc};
pub use rust_decimal::Decimal;
use rust_decimal::prelude::ToPrimitive;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DataType {
    Bool,
    I64,
    U64,
    F64,
    Decimal,
    Text,
    Json,
    Date,
    Timestamp,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Value {
    Null,
    Bool(bool),
    I64(i64),
    U64(u64),
    F64(f64),
    Decimal(Decimal),
    Text(String),
    Json(serde_json::Value),
    Date(NaiveDate),
    Timestamp(DateTime<Utc>),
    Object(BTreeMap<String, Value>),
    List(Vec<Value>),
}

impl From<&str> for Value {
    fn from(value: &str) -> Self {
        Self::Text(value.to_owned())
    }
}

impl From<String> for Value {
    fn from(value: String) -> Self {
        Self::Text(value)
    }
}

impl From<i64> for Value {
    fn from(value: i64) -> Self {
        Self::I64(value)
    }
}

impl From<i32> for Value {
    fn from(value: i32) -> Self {
        Self::I64(i64::from(value))
    }
}

impl From<i16> for Value {
    fn from(value: i16) -> Self {
        Self::I64(i64::from(value))
    }
}

impl From<u64> for Value {
    fn from(value: u64) -> Self {
        Self::U64(value)
    }
}

impl From<u32> for Value {
    fn from(value: u32) -> Self {
        Self::U64(u64::from(value))
    }
}

impl From<u16> for Value {
    fn from(value: u16) -> Self {
        Self::U64(u64::from(value))
    }
}

impl From<f64> for Value {
    fn from(value: f64) -> Self {
        Self::F64(value)
    }
}

impl From<f32> for Value {
    fn from(value: f32) -> Self {
        Self::F64(f64::from(value))
    }
}

impl From<bool> for Value {
    fn from(value: bool) -> Self {
        Self::Bool(value)
    }
}

impl From<Decimal> for Value {
    fn from(value: Decimal) -> Self {
        Self::Decimal(value)
    }
}

impl From<serde_json::Value> for Value {
    fn from(value: serde_json::Value) -> Self {
        Self::Json(value)
    }
}

impl From<NaiveDate> for Value {
    fn from(value: NaiveDate) -> Self {
        Self::Date(value)
    }
}

impl From<DateTime<Utc>> for Value {
    fn from(value: DateTime<Utc>) -> Self {
        Self::Timestamp(value)
    }
}

impl Value {
    pub fn object(record: crate::Record) -> Self {
        Self::Object(record)
    }

    pub fn try_i64(&self) -> Option<i64> {
        match self {
            Self::I64(value) => Some(*value),
            Self::U64(value) => i64::try_from(*value).ok(),
            Self::Decimal(value) => value.to_i64(),
            _ => None,
        }
    }

    pub fn try_u64(&self) -> Option<u64> {
        match self {
            Self::U64(value) => Some(*value),
            Self::I64(value) => u64::try_from(*value).ok(),
            Self::Decimal(value) => value.to_u64(),
            _ => None,
        }
    }

    pub fn try_decimal(&self) -> Option<Decimal> {
        match self {
            Self::Decimal(value) => Some(*value),
            Self::I64(value) => Some(Decimal::from(*value)),
            Self::U64(value) => Some(Decimal::from(*value)),
            Self::Text(value) => Decimal::from_str(value).ok(),
            _ => None,
        }
    }

    pub fn try_f64(&self) -> Option<f64> {
        match self {
            Self::F64(value) => Some(*value),
            Self::I64(value) => Some(*value as f64),
            Self::U64(value) => Some(*value as f64),
            Self::Decimal(value) => value.to_f64(),
            _ => None,
        }
    }

    pub fn try_text(&self) -> Option<&str> {
        match self {
            Self::Text(value) => Some(value),
            _ => None,
        }
    }

    pub fn try_bool(&self) -> Option<bool> {
        match self {
            Self::Bool(value) => Some(*value),
            _ => None,
        }
    }

    pub fn try_date(&self) -> Option<NaiveDate> {
        match self {
            Self::Date(value) => Some(*value),
            Self::Text(value) => {
                if let Ok(nd) = chrono::NaiveDate::parse_from_str(value, "%Y-%m-%d") {
                    return Some(nd);
                }
                None
            }
            _ => None,
        }
    }

    pub fn try_timestamp(&self) -> Option<DateTime<Utc>> {
        match self {
            Self::Timestamp(value) => Some(*value),
            Self::Text(value) => {
                if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(value) {
                    return Some(dt.with_timezone(&chrono::Utc));
                }
                if let Ok(ndt) = chrono::NaiveDateTime::parse_from_str(value, "%Y-%m-%d %H:%M:%S") {
                    return Some(chrono::DateTime::from_naive_utc_and_offset(ndt, chrono::Utc));
                }
                if let Ok(nd) = chrono::NaiveDate::parse_from_str(value, "%Y-%m-%d") {
                    let ndt = nd.and_hms_opt(0, 0, 0)?;
                    return Some(chrono::DateTime::from_naive_utc_and_offset(ndt, chrono::Utc));
                }
                None
            }
            _ => None,
        }
    }

    pub fn to_json_value(&self) -> serde_json::Value {
        match self {
            Self::Null => serde_json::Value::Null,
            Self::Bool(value) => serde_json::Value::Bool(*value),
            Self::I64(value) => serde_json::Value::from(*value),
            Self::U64(value) => serde_json::Value::from(*value),
            Self::F64(value) => serde_json::Number::from_f64(*value)
                .map(serde_json::Value::Number)
                .unwrap_or(serde_json::Value::Null),
            Self::Decimal(value) => serde_json::Value::String(value.to_string()),
            Self::Text(value) => serde_json::Value::String(value.clone()),
            Self::Json(value) => value.clone(),
            Self::Date(value) => serde_json::Value::String(value.to_string()),
            Self::Timestamp(value) => serde_json::Value::String(value.to_rfc3339()),
            Self::Object(record) => crate::record_to_json_value(record),
            Self::List(values) => {
                serde_json::Value::Array(values.iter().map(Value::to_json_value).collect())
            }
        }
    }
}
