//! Row representation for SQL tables.

use super::types::{SqlError, SqlValue};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Unique row identifier.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct RowId(pub u64);

impl RowId {
    /// Creates a new row ID.
    pub fn new(id: u64) -> Self {
        RowId(id)
    }

    /// Returns the inner ID value.
    pub fn inner(&self) -> u64 {
        self.0
    }
}

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

/// A single row in a SQL table.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Row {
    /// Row identifier.
    pub id: RowId,
    /// Column values indexed by column name.
    values: HashMap<String, SqlValue>,
    /// Ordered column names (preserves insertion order).
    columns: Vec<String>,
}

impl Row {
    /// Creates a new empty row.
    pub fn new(id: RowId) -> Self {
        Self {
            id,
            values: HashMap::new(),
            columns: Vec::new(),
        }
    }

    /// Creates a row with given columns.
    pub fn with_columns(id: RowId, columns: Vec<String>) -> Self {
        let mut values = HashMap::with_capacity(columns.len());
        for col in &columns {
            values.insert(col.clone(), SqlValue::Null);
        }
        Self {
            id,
            values,
            columns,
        }
    }

    /// Sets a column value.
    pub fn set(&mut self, column: &str, value: SqlValue) {
        if !self.values.contains_key(column) {
            self.columns.push(column.to_string());
        }
        self.values.insert(column.to_string(), value);
    }

    /// Gets a column value.
    pub fn get(&self, column: &str) -> Option<&SqlValue> {
        self.values.get(column)
    }

    /// Gets a column value or returns Null.
    pub fn get_or_null(&self, column: &str) -> SqlValue {
        self.values.get(column).cloned().unwrap_or(SqlValue::Null)
    }

    /// Returns all column names.
    pub fn columns(&self) -> &[String] {
        &self.columns
    }

    /// Returns all values in column order.
    pub fn values(&self) -> Vec<&SqlValue> {
        self.columns.iter().map(|c| self.values.get(c).unwrap()).collect()
    }

    /// Returns the number of columns.
    pub fn len(&self) -> usize {
        self.columns.len()
    }

    /// Returns true if the row has no columns.
    pub fn is_empty(&self) -> bool {
        self.columns.is_empty()
    }

    /// Projects to specific columns.
    pub fn project(&self, columns: &[String]) -> Row {
        let mut row = Row::new(self.id);
        for col in columns {
            if let Some(value) = self.values.get(col) {
                row.set(col, value.clone());
            }
        }
        row
    }

    /// Converts to a map.
    pub fn to_map(&self) -> HashMap<String, SqlValue> {
        self.values.clone()
    }

    /// Converts from a map.
    pub fn from_map(id: RowId, map: HashMap<String, SqlValue>) -> Self {
        let columns: Vec<String> = map.keys().cloned().collect();
        Self {
            id,
            values: map,
            columns,
        }
    }
}

impl PartialEq for Row {
    fn eq(&self, other: &Self) -> bool {
        if self.columns.len() != other.columns.len() {
            return false;
        }
        for col in &self.columns {
            if self.get(col) != other.get(col) {
                return false;
            }
        }
        true
    }
}

/// Builder for creating rows.
pub struct RowBuilder {
    id: RowId,
    values: Vec<(String, SqlValue)>,
}

impl RowBuilder {
    /// Creates a new row builder.
    pub fn new(id: RowId) -> Self {
        Self {
            id,
            values: Vec::new(),
        }
    }

    /// Adds a column value.
    pub fn column(mut self, name: impl Into<String>, value: SqlValue) -> Self {
        self.values.push((name.into(), value));
        self
    }

    /// Adds an integer column.
    pub fn int(self, name: impl Into<String>, value: i64) -> Self {
        self.column(name, SqlValue::Integer(value))
    }

    /// Adds a real column.
    pub fn real(self, name: impl Into<String>, value: f64) -> Self {
        self.column(name, SqlValue::Real(value))
    }

    /// Adds a text column.
    pub fn text(self, name: impl Into<String>, value: impl Into<String>) -> Self {
        self.column(name, SqlValue::Text(value.into()))
    }

    /// Adds a boolean column.
    pub fn boolean(self, name: impl Into<String>, value: bool) -> Self {
        self.column(name, SqlValue::Boolean(value))
    }

    /// Adds a null column.
    pub fn null(self, name: impl Into<String>) -> Self {
        self.column(name, SqlValue::Null)
    }

    /// Builds the row.
    pub fn build(self) -> Row {
        let mut row = Row::new(self.id);
        for (name, value) in self.values {
            row.set(&name, value);
        }
        row
    }
}

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

    #[test]
    fn test_row_basic() {
        let mut row = Row::new(RowId(1));
        row.set("name", SqlValue::Text("Alice".to_string()));
        row.set("age", SqlValue::Integer(30));

        assert_eq!(row.get("name"), Some(&SqlValue::Text("Alice".to_string())));
        assert_eq!(row.get("age"), Some(&SqlValue::Integer(30)));
        assert_eq!(row.get("missing"), None);
        assert_eq!(row.len(), 2);
    }

    #[test]
    fn test_row_builder() {
        let row = RowBuilder::new(RowId(1))
            .text("name", "Bob")
            .int("age", 25)
            .boolean("active", true)
            .build();

        assert_eq!(row.get("name"), Some(&SqlValue::Text("Bob".to_string())));
        assert_eq!(row.get("age"), Some(&SqlValue::Integer(25)));
        assert_eq!(row.get("active"), Some(&SqlValue::Boolean(true)));
    }

    #[test]
    fn test_row_projection() {
        let row = RowBuilder::new(RowId(1))
            .text("a", "1")
            .text("b", "2")
            .text("c", "3")
            .build();

        let projected = row.project(&["a".to_string(), "c".to_string()]);
        assert_eq!(projected.len(), 2);
        assert!(projected.get("a").is_some());
        assert!(projected.get("b").is_none());
        assert!(projected.get("c").is_some());
    }
}
