use crate::schema::model::{ColumnDefault, DefaultLiteral, LogicalType, Schema, Table};
use crate::{DbnestError, Result};
use std::collections::HashSet;

const IDENTIFIER_RULE: &str = "^[A-Za-z_][A-Za-z0-9_]*$";

pub fn validate_schema(schema: &Schema) -> Result<()> {
    if schema.tables.is_empty() {
        return Err(DbnestError::InvalidArgument("schema has no tables".into()));
    }

    let mut table_names = HashSet::new();
    let mut index_names = HashSet::new();
    for t in &schema.tables {
        validate_identifier("table", &t.name)?;
        validate_table(t, &mut index_names)?;
        if !table_names.insert(t.name.clone()) {
            return Err(DbnestError::InvalidArgument(format!(
                "duplicate table name '{}'",
                t.name
            )));
        }
    }

    Ok(())
}

fn validate_table(t: &Table, index_names: &mut HashSet<String>) -> Result<()> {
    if t.columns.is_empty() {
        return Err(DbnestError::InvalidArgument(format!(
            "table '{}' has no columns",
            t.name
        )));
    }

    let mut col_names = HashSet::new();
    let mut pk_count = 0usize;

    for c in &t.columns {
        validate_identifier("column", &c.name)?;
        if !col_names.insert(c.name.clone()) {
            return Err(DbnestError::InvalidArgument(format!(
                "duplicate column '{}' in table '{}'",
                c.name, t.name
            )));
        }
        if c.primary_key {
            pk_count += 1;
        }
        validate_default(&c.col_type, c.default.as_ref(), &c.name, &t.name)?;
    }

    if pk_count > 1 {
        return Err(DbnestError::InvalidArgument(format!(
            "table '{}' has multiple primary keys (v1 supports only single-column PK)",
            t.name
        )));
    }

    for idx in &t.indexes {
        validate_identifier("index", &idx.name)?;
        if !index_names.insert(idx.name.clone()) {
            return Err(DbnestError::InvalidArgument(format!(
                "duplicate index name '{}'",
                idx.name
            )));
        }
        if idx.columns.is_empty() {
            return Err(DbnestError::InvalidArgument(format!(
                "index '{}' on table '{}' has no columns",
                idx.name, t.name
            )));
        }
        let mut idx_cols = HashSet::new();
        for col in &idx.columns {
            if !idx_cols.insert(col.clone()) {
                return Err(DbnestError::InvalidArgument(format!(
                    "index '{}' on table '{}' contains duplicate column '{}'",
                    idx.name, t.name, col
                )));
            }
            if !col_names.contains(col) {
                return Err(DbnestError::InvalidArgument(format!(
                    "index '{}' on table '{}' references unknown column '{}'",
                    idx.name, t.name, col
                )));
            }
        }
    }

    Ok(())
}

fn validate_identifier(kind: &str, name: &str) -> Result<()> {
    if is_valid_identifier(name) {
        return Ok(());
    }

    Err(DbnestError::InvalidArgument(format!(
        "invalid {kind} identifier '{name}' (expected {IDENTIFIER_RULE})"
    )))
}

fn is_valid_identifier(name: &str) -> bool {
    let mut chars = name.chars();
    let Some(first) = chars.next() else {
        return false;
    };
    if !(first == '_' || first.is_ascii_alphabetic()) {
        return false;
    }
    chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
}

fn validate_default(
    col_type: &LogicalType,
    default: Option<&ColumnDefault>,
    column_name: &str,
    table_name: &str,
) -> Result<()> {
    let Some(default) = default else {
        return Ok(());
    };

    match default {
        ColumnDefault::Sql { sql } => {
            if sql.trim().is_empty() {
                return Err(DbnestError::InvalidArgument(format!(
                    "raw SQL default for column '{}' on table '{}' is empty",
                    column_name, table_name
                )));
            }
        }
        ColumnDefault::Value(DefaultLiteral::String(value)) => match col_type {
            LogicalType::Int64 if value.parse::<i64>().is_err() => {
                return Err(DbnestError::InvalidArgument(format!(
                    "default for int64 column '{}' on table '{}' must be an integer",
                    column_name, table_name
                )));
            }
            LogicalType::Bool if parse_bool(value).is_none() => {
                return Err(DbnestError::InvalidArgument(format!(
                    "default for bool column '{}' on table '{}' must be true/false/1/0",
                    column_name, table_name
                )));
            }
            _ => {}
        },
        ColumnDefault::Value(DefaultLiteral::Int(_)) => {
            if !matches!(col_type, LogicalType::Int64) {
                return Err(DbnestError::InvalidArgument(format!(
                    "numeric default for column '{}' on table '{}' requires int64 type",
                    column_name, table_name
                )));
            }
        }
        ColumnDefault::Value(DefaultLiteral::Bool(_)) => {
            if !matches!(col_type, LogicalType::Bool) {
                return Err(DbnestError::InvalidArgument(format!(
                    "boolean default for column '{}' on table '{}' requires bool type",
                    column_name, table_name
                )));
            }
        }
    }

    Ok(())
}

pub(crate) fn parse_bool(value: &str) -> Option<bool> {
    match value.to_ascii_lowercase().as_str() {
        "true" | "1" => Some(true),
        "false" | "0" => Some(false),
        _ => None,
    }
}
