//! Core traits for ClickType

use std::borrow::Cow;
use std::io::{Read, Write};

use crate::error::{ReadError, WriteError};

/// Trait for types that can be mapped to/from ClickHouse types
pub trait ClickHouseType: Sized + Send + Sync + 'static {
    /// Name of the type in ClickHouse (e.g., "UInt64", "Nullable(String)")
    fn type_name() -> Cow<'static, str>;

    /// Write the value in RowBinary format
    fn write_binary<W: Write>(&self, writer: &mut W) -> Result<(), WriteError>;

    /// Read the value from RowBinary format
    fn read_binary<R: Read>(reader: &mut R) -> Result<Self, ReadError>;

    /// Whether this type is nullable (for optimizations)
    fn is_nullable() -> bool {
        false
    }

    /// Fixed size in bytes, if applicable (for buffer optimizations)
    fn fixed_size() -> Option<usize> {
        None
    }

    /// Convert to SQL literal string
    fn to_sql_literal(&self) -> String
    where
        Self: ToString,
    {
        self.to_string()
    }
}

/// Trait for table structs generated by `#[derive(ClickTable)]`
pub trait ClickTable: Sized + Send + Sync + 'static {
    /// Associated columns type
    type Columns;

    /// Table name in ClickHouse
    fn table_name() -> &'static str;

    /// Get column accessors
    fn columns() -> Self::Columns;

    /// List of all column names
    fn column_names() -> &'static [&'static str];

    /// List of columns that can be inserted (excludes MATERIALIZED, ALIAS)
    fn insertable_columns() -> &'static [&'static str];

    /// List of primary key columns
    fn primary_key_columns() -> &'static [&'static str];

    /// Schema definition: list of (column_name, clickhouse_type)
    /// Used for validation against the actual ClickHouse table
    fn schema() -> Vec<(&'static str, String)>;

    /// Generate CREATE TABLE DDL statement
    fn create_table_ddl() -> String {
        let mut ddl = format!("CREATE TABLE {} (\n", Self::table_name());

        // Add columns (this is a default implementation, can be overridden)
        let col_names = Self::column_names();
        for (i, col_name) in col_names.iter().enumerate() {
            if i > 0 {
                ddl.push_str(",\n");
            }
            ddl.push_str(&format!("    {} String", col_name));
        }

        ddl.push_str("\n) ENGINE = MergeTree()");

        // Add primary key if exists
        let pk_cols = Self::primary_key_columns();
        if !pk_cols.is_empty() {
            ddl.push_str(&format!("\nPRIMARY KEY ({})", pk_cols.join(", ")));
        }

        ddl.push_str("\nORDER BY ");
        if !pk_cols.is_empty() {
            ddl.push_str(&format!("({})", pk_cols.join(", ")));
        } else {
            ddl.push_str("tuple()");
        }

        ddl
    }
}

/// Trait for types that can be inserted into ClickHouse
pub trait ClickInsertable {
    /// Write a row in RowBinary format
    fn write_row<W: Write>(&self, writer: &mut W) -> Result<(), WriteError>;
}

/// Trait for types that can be read from ClickHouse query results
pub trait ClickReadable: Sized {
    /// Read a row from RowBinary format
    fn read_row<R: Read>(reader: &mut R) -> Result<Self, ReadError>;
}

/// Trait for typed column accessors
pub trait TypedColumn: Copy + Send + Sync + 'static {
    /// The table this column belongs to
    type Table: ClickTable;

    /// The Rust type of this column
    type Type: ClickHouseType;

    /// Column name (unqualified)
    fn name() -> &'static str;

    /// Fully qualified column name (table.column)
    fn qualified_name() -> &'static str;

    /// Whether this column is part of the primary key
    fn is_primary_key() -> bool {
        false
    }
}

/// Marker trait for numeric types
pub trait Numeric: ClickHouseType {}

impl Numeric for i8 {}
impl Numeric for i16 {}
impl Numeric for i32 {}
impl Numeric for i64 {}
impl Numeric for i128 {}
impl Numeric for u8 {}
impl Numeric for u16 {}
impl Numeric for u32 {}
impl Numeric for u64 {}
impl Numeric for u128 {}
impl Numeric for f32 {}
impl Numeric for f64 {}

/// Marker trait for float types
pub trait Float: Numeric {}

impl Float for f32 {}
impl Float for f64 {}
