//! SQL type system.

use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
use thiserror::Error;

/// SQL data types.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum SqlType {
    /// 64-bit signed integer.
    Integer,
    /// 64-bit floating point.
    Real,
    /// UTF-8 text string.
    Text,
    /// Binary data.
    Blob,
    /// Boolean value.
    Boolean,
    /// Unix timestamp in milliseconds.
    Timestamp,
    /// Null type.
    Null,
}

impl SqlType {
    /// Parses type from string.
    pub fn from_str(s: &str) -> Option<Self> {
        match s.to_uppercase().as_str() {
            "INTEGER" | "INT" | "BIGINT" | "SMALLINT" => Some(SqlType::Integer),
            "REAL" | "FLOAT" | "DOUBLE" => Some(SqlType::Real),
            "TEXT" | "VARCHAR" | "CHAR" | "STRING" => Some(SqlType::Text),
            "BLOB" | "BINARY" | "BYTES" => Some(SqlType::Blob),
            "BOOLEAN" | "BOOL" => Some(SqlType::Boolean),
            "TIMESTAMP" | "DATETIME" | "DATE" => Some(SqlType::Timestamp),
            _ => None,
        }
    }

    /// Returns the default value for this type.
    pub fn default_value(&self) -> SqlValue {
        match self {
            SqlType::Integer => SqlValue::Integer(0),
            SqlType::Real => SqlValue::Real(0.0),
            SqlType::Text => SqlValue::Text(String::new()),
            SqlType::Blob => SqlValue::Blob(Vec::new()),
            SqlType::Boolean => SqlValue::Boolean(false),
            SqlType::Timestamp => SqlValue::Timestamp(0),
            SqlType::Null => SqlValue::Null,
        }
    }
}

/// SQL value types.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum SqlValue {
    /// Null value.
    Null,
    /// Integer value.
    Integer(i64),
    /// Real (floating point) value.
    Real(f64),
    /// Text string value.
    Text(String),
    /// Binary blob value.
    Blob(Vec<u8>),
    /// Boolean value.
    Boolean(bool),
    /// Timestamp value (Unix ms).
    Timestamp(u64),
}

impl SqlValue {
    /// Returns the SQL type of this value.
    pub fn sql_type(&self) -> SqlType {
        match self {
            SqlValue::Null => SqlType::Null,
            SqlValue::Integer(_) => SqlType::Integer,
            SqlValue::Real(_) => SqlType::Real,
            SqlValue::Text(_) => SqlType::Text,
            SqlValue::Blob(_) => SqlType::Blob,
            SqlValue::Boolean(_) => SqlType::Boolean,
            SqlValue::Timestamp(_) => SqlType::Timestamp,
        }
    }

    /// Returns true if this is a null value.
    pub fn is_null(&self) -> bool {
        matches!(self, SqlValue::Null)
    }

    /// Converts to integer if possible.
    pub fn as_integer(&self) -> Option<i64> {
        match self {
            SqlValue::Integer(i) => Some(*i),
            SqlValue::Real(f) => Some(*f as i64),
            SqlValue::Boolean(b) => Some(if *b { 1 } else { 0 }),
            SqlValue::Timestamp(t) => Some(*t as i64),
            _ => None,
        }
    }

    /// Converts to real if possible.
    pub fn as_real(&self) -> Option<f64> {
        match self {
            SqlValue::Integer(i) => Some(*i as f64),
            SqlValue::Real(f) => Some(*f),
            SqlValue::Timestamp(t) => Some(*t as f64),
            _ => None,
        }
    }

    /// Converts to text if possible.
    pub fn as_text(&self) -> Option<&str> {
        match self {
            SqlValue::Text(s) => Some(s),
            _ => None,
        }
    }

    /// Converts to boolean if possible.
    pub fn as_boolean(&self) -> Option<bool> {
        match self {
            SqlValue::Boolean(b) => Some(*b),
            SqlValue::Integer(i) => Some(*i != 0),
            _ => None,
        }
    }

    /// Converts to JSON value.
    pub fn to_json(&self) -> JsonValue {
        match self {
            SqlValue::Null => JsonValue::Null,
            SqlValue::Integer(i) => JsonValue::Number((*i).into()),
            SqlValue::Real(f) => serde_json::Number::from_f64(*f)
                .map(JsonValue::Number)
                .unwrap_or(JsonValue::Null),
            SqlValue::Text(s) => JsonValue::String(s.clone()),
            SqlValue::Blob(b) => JsonValue::String(hex::encode(b)),
            SqlValue::Boolean(b) => JsonValue::Bool(*b),
            SqlValue::Timestamp(t) => JsonValue::Number((*t).into()),
        }
    }

    /// Returns a numeric type order for comparison purposes.
    fn type_order(&self) -> u8 {
        match self {
            SqlValue::Null => 0,
            SqlValue::Boolean(_) => 1,
            SqlValue::Integer(_) => 2,
            SqlValue::Real(_) => 3,
            SqlValue::Text(_) => 4,
            SqlValue::Blob(_) => 5,
            SqlValue::Timestamp(_) => 6,
        }
    }

    /// Creates from JSON value.
    pub fn from_json(value: &JsonValue) -> Self {
        match value {
            JsonValue::Null => SqlValue::Null,
            JsonValue::Bool(b) => SqlValue::Boolean(*b),
            JsonValue::Number(n) => {
                if let Some(i) = n.as_i64() {
                    SqlValue::Integer(i)
                } else if let Some(f) = n.as_f64() {
                    SqlValue::Real(f)
                } else {
                    SqlValue::Null
                }
            }
            JsonValue::String(s) => SqlValue::Text(s.clone()),
            _ => SqlValue::Text(value.to_string()),
        }
    }
}

impl PartialEq for SqlValue {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (SqlValue::Null, SqlValue::Null) => true,
            (SqlValue::Integer(a), SqlValue::Integer(b)) => a == b,
            (SqlValue::Real(a), SqlValue::Real(b)) => a == b,
            (SqlValue::Text(a), SqlValue::Text(b)) => a == b,
            (SqlValue::Blob(a), SqlValue::Blob(b)) => a == b,
            (SqlValue::Boolean(a), SqlValue::Boolean(b)) => a == b,
            (SqlValue::Timestamp(a), SqlValue::Timestamp(b)) => a == b,
            // Cross-type comparisons
            (SqlValue::Integer(a), SqlValue::Real(b)) => (*a as f64) == *b,
            (SqlValue::Real(a), SqlValue::Integer(b)) => *a == (*b as f64),
            _ => false,
        }
    }
}

impl Eq for SqlValue {}

impl Hash for SqlValue {
    fn hash<H: Hasher>(&self, state: &mut H) {
        std::mem::discriminant(self).hash(state);
        match self {
            SqlValue::Null => {}
            SqlValue::Integer(i) => i.hash(state),
            SqlValue::Real(f) => f.to_bits().hash(state),
            SqlValue::Text(s) => s.hash(state),
            SqlValue::Blob(b) => b.hash(state),
            SqlValue::Boolean(b) => b.hash(state),
            SqlValue::Timestamp(t) => t.hash(state),
        }
    }
}

impl PartialOrd for SqlValue {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for SqlValue {
    fn cmp(&self, other: &Self) -> Ordering {
        match (self, other) {
            (SqlValue::Null, SqlValue::Null) => Ordering::Equal,
            (SqlValue::Null, _) => Ordering::Less,
            (_, SqlValue::Null) => Ordering::Greater,
            (SqlValue::Integer(a), SqlValue::Integer(b)) => a.cmp(b),
            (SqlValue::Real(a), SqlValue::Real(b)) => {
                // Convert to bits for total ordering (handles NaN)
                a.to_bits().cmp(&b.to_bits())
            }
            (SqlValue::Text(a), SqlValue::Text(b)) => a.cmp(b),
            (SqlValue::Blob(a), SqlValue::Blob(b)) => a.cmp(b),
            (SqlValue::Boolean(a), SqlValue::Boolean(b)) => a.cmp(b),
            (SqlValue::Timestamp(a), SqlValue::Timestamp(b)) => a.cmp(b),
            (SqlValue::Integer(a), SqlValue::Real(b)) => {
                (*a as f64).to_bits().cmp(&b.to_bits())
            }
            (SqlValue::Real(a), SqlValue::Integer(b)) => {
                a.to_bits().cmp(&(*b as f64).to_bits())
            }
            // Different types: order by type discriminant
            _ => self.type_order().cmp(&other.type_order()),
        }
    }
}

impl Default for SqlValue {
    fn default() -> Self {
        SqlValue::Null
    }
}

impl std::fmt::Display for SqlValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SqlValue::Null => write!(f, "NULL"),
            SqlValue::Integer(i) => write!(f, "{}", i),
            SqlValue::Real(r) => write!(f, "{}", r),
            SqlValue::Text(s) => write!(f, "'{}'", s),
            SqlValue::Blob(b) => write!(f, "X'{}'", hex::encode(b)),
            SqlValue::Boolean(b) => write!(f, "{}", if *b { "TRUE" } else { "FALSE" }),
            SqlValue::Timestamp(t) => write!(f, "{}", t),
        }
    }
}

/// SQL errors.
#[derive(Debug, Error)]
pub enum SqlError {
    /// Parse error.
    #[error("Parse error: {0}")]
    Parse(String),

    /// Table not found.
    #[error("Table not found: {0}")]
    TableNotFound(String),

    /// Table already exists.
    #[error("Table already exists: {0}")]
    TableExists(String),

    /// Column not found.
    #[error("Column not found: {0}")]
    ColumnNotFound(String),

    /// Type mismatch.
    #[error("Type mismatch: expected {expected}, got {got}")]
    TypeMismatch { expected: String, got: String },

    /// Constraint violation.
    #[error("Constraint violation: {0}")]
    ConstraintViolation(String),

    /// Primary key violation.
    #[error("Primary key violation: duplicate key {0}")]
    PrimaryKeyViolation(String),

    /// Not null violation.
    #[error("Not null violation: column {0} cannot be null")]
    NotNullViolation(String),

    /// Transaction error.
    #[error("Transaction error: {0}")]
    Transaction(String),

    /// Invalid operation.
    #[error("Invalid operation: {0}")]
    InvalidOperation(String),

    /// Unsupported feature.
    #[error("Unsupported: {0}")]
    Unsupported(String),

    /// Internal error.
    #[error("Internal error: {0}")]
    Internal(String),
}

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

    #[test]
    fn test_sql_type_from_str() {
        assert_eq!(SqlType::from_str("INTEGER"), Some(SqlType::Integer));
        assert_eq!(SqlType::from_str("int"), Some(SqlType::Integer));
        assert_eq!(SqlType::from_str("TEXT"), Some(SqlType::Text));
        assert_eq!(SqlType::from_str("varchar"), Some(SqlType::Text));
        assert_eq!(SqlType::from_str("BOOLEAN"), Some(SqlType::Boolean));
        assert_eq!(SqlType::from_str("unknown"), None);
    }

    #[test]
    fn test_sql_value_conversions() {
        let int_val = SqlValue::Integer(42);
        assert_eq!(int_val.as_integer(), Some(42));
        assert_eq!(int_val.as_real(), Some(42.0));

        let real_val = SqlValue::Real(3.14);
        assert_eq!(real_val.as_real(), Some(3.14));
        assert_eq!(real_val.as_integer(), Some(3));

        let text_val = SqlValue::Text("hello".to_string());
        assert_eq!(text_val.as_text(), Some("hello"));
    }

    #[test]
    fn test_sql_value_comparison() {
        assert_eq!(SqlValue::Integer(5), SqlValue::Integer(5));
        assert!(SqlValue::Integer(5) < SqlValue::Integer(10));
        assert!(SqlValue::Text("a".to_string()) < SqlValue::Text("b".to_string()));
        assert!(SqlValue::Null < SqlValue::Integer(0));
    }

    #[test]
    fn test_sql_value_json() {
        let val = SqlValue::Integer(42);
        assert_eq!(val.to_json(), serde_json::json!(42));

        let val = SqlValue::Text("hello".to_string());
        assert_eq!(val.to_json(), serde_json::json!("hello"));

        let json = serde_json::json!(true);
        let val = SqlValue::from_json(&json);
        assert_eq!(val, SqlValue::Boolean(true));
    }
}
