//! Column resolution: field generator, aggregator, column reference, or expression.

use seedfaker_core::field::{self, Field, Ordering, RangeSpec, Transform};

pub struct Column {
    pub name: String,
    pub gen: ColumnGen,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AggrFunc {
    Sum,
    Count,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExprOp {
    Add,
    Sub,
    Mul,
}

/// One side of an arithmetic expression.
pub enum ExprOperand {
    /// Reference to a declared column — reads `raw_values[idx]` at runtime.
    Col(String),
    /// Inline field generator — produces a fresh raw value each record.
    Field { field: &'static Field, modifier: String, range: Option<RangeSpec> },
}

impl std::fmt::Debug for ExprOperand {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Col(name) => write!(f, "Col({name})"),
            Self::Field { field, modifier, range } => {
                write!(f, "Field({}", field.name)?;
                if let Some(r) = range {
                    write!(f, ":{r:?}")?;
                }
                if !modifier.is_empty() {
                    write!(f, ":{modifier}")?;
                }
                write!(f, ")")
            }
        }
    }
}

pub enum ColumnGen {
    Field {
        field: &'static Field,
        modifier: String,
        transform: Transform,
        range: Option<RangeSpec>,
        ordering: Ordering,
    },
    Literal(String),
    /// Aggregator: running aggregate over a source column.
    Aggr {
        func: AggrFunc,
        source_col: String,
        group_by: Option<String>,
    },
    /// Reference to another column. Empty modifier = copy value as-is.
    /// With modifier = reformat the raw value (e.g. `total:usd`, `hire:us`).
    Ref {
        source_col: String,
        modifier: String,
    },
    /// Arithmetic expression: left OP right.
    /// Result stored in `raw_values`, formatted via `format_raw`.
    Expr {
        left: ExprOperand,
        op: ExprOp,
        right: ExprOperand,
        result_type: ExprResultType,
    },
}

impl std::fmt::Debug for ColumnGen {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Field { field, modifier, transform, range, .. } => {
                write!(f, "Field({}", field.name)?;
                if let Some(r) = range {
                    write!(f, ":{r:?}")?;
                }
                if !modifier.is_empty() {
                    write!(f, ":{modifier}")?;
                }
                if *transform != Transform::None {
                    write!(f, ":{transform:?}")?;
                }
                write!(f, ")")
            }
            Self::Literal(s) => write!(f, "Literal({s:?})"),
            Self::Aggr { func, source_col, group_by } => {
                write!(f, "Aggr({func:?}({source_col}")?;
                if let Some(g) = group_by {
                    write!(f, ", {g}")?;
                }
                write!(f, "))")
            }
            Self::Ref { source_col, modifier } => {
                write!(f, "Ref({source_col}")?;
                if !modifier.is_empty() {
                    write!(f, ":{modifier}")?;
                }
                write!(f, ")")
            }
            Self::Expr { left, op, right, result_type } => {
                write!(f, "Expr({left:?} {op:?} {right:?} -> {result_type:?})")
            }
        }
    }
}

pub struct ColumnSet {
    columns: Vec<Column>,
}

impl ColumnSet {
    pub fn new(columns: Vec<Column>) -> Self {
        Self { columns }
    }

    pub fn len(&self) -> usize {
        self.columns.len()
    }

    pub fn is_empty(&self) -> bool {
        self.columns.is_empty()
    }

    pub fn iter(&self) -> std::slice::Iter<'_, Column> {
        self.columns.iter()
    }

    pub fn names(&self) -> Vec<&str> {
        self.columns.iter().map(|v| v.name.as_str()).collect()
    }

    pub fn index_of(&self, name: &str) -> Option<usize> {
        self.columns.iter().position(|v| v.name == name)
    }
}

impl<'a> IntoIterator for &'a ColumnSet {
    type Item = &'a Column;
    type IntoIter = std::slice::Iter<'a, Column>;

    fn into_iter(self) -> Self::IntoIter {
        self.columns.iter()
    }
}

/// Data type of a field for arithmetic validation.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldType {
    Int,
    Float,
    Money,
    Date,
    Timestamp,
    Text,
}

/// Classify a field by name.
pub fn field_type(name: &str) -> FieldType {
    match name {
        "integer" | "age" | "serial" | "digit" | "bit" | "trit" | "dice" | "port" | "latency" => {
            FieldType::Int
        }
        "float" | "latitude" | "longitude" => FieldType::Float,
        "amount" => FieldType::Money,
        "date" | "birthdate" => FieldType::Date,
        "timestamp" => FieldType::Timestamp,
        _ => FieldType::Text,
    }
}

/// Result type of an expression — determines formatting and delta conversion.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExprResultType {
    Int,
    Float,
    Money,
    Date,
    Timestamp,
}

/// Validate and infer result type for `left OP right`.
/// Returns error message if the combination is invalid.
pub fn check_expr_types(
    left: FieldType,
    op: ExprOp,
    right: FieldType,
) -> Result<ExprResultType, &'static str> {
    use ExprOp::{Add, Mul, Sub};
    use FieldType::{Date, Float, Int, Money, Text, Timestamp};
    match (left, op, right) {
        // int ↔ int
        (Int, Add | Sub | Mul, Int) => Ok(ExprResultType::Int),
        // float ↔ float, int ↔ float
        (Float, Add | Sub | Mul, Float) => Ok(ExprResultType::Float),
        (Float, Add | Sub | Mul, Int) | (Int, Add | Sub | Mul, Float) => Ok(ExprResultType::Float),
        // money ↔ money (no multiply)
        (Money, Add | Sub, Money) => Ok(ExprResultType::Money),
        (Money, Mul, Money) => Err("cannot multiply money by money"),
        // money ↔ int/float
        (Money, Add | Sub | Mul, Int | Float) => Ok(ExprResultType::Money),
        // int/float × money (commutative multiply only)
        (Int | Float, Mul, Money) => Ok(ExprResultType::Money),
        // int/float +/- money is ambiguous
        (Int | Float, Add | Sub, Money) => {
            Err("cannot add/subtract int and money; put money on the left")
        }
        // date/timestamp + int, date/timestamp - int
        (Date, Add | Sub, Int) => Ok(ExprResultType::Date),
        (Timestamp, Add | Sub, Int) => Ok(ExprResultType::Timestamp),
        // date/timestamp forbidden combos
        (Date | Timestamp, Mul, _) => Err("cannot multiply dates or timestamps"),
        (Date | Timestamp, _, Float) => {
            Err("date/timestamp arithmetic requires integer (whole days or seconds)")
        }
        (Date | Timestamp, _, Money) => Err("cannot combine date/timestamp with money"),
        (Date | Timestamp, _, Date | Timestamp) => Err("cannot combine two date/timestamp values"),
        // anything with text
        (Text, _, _) | (_, _, Text) => Err("field does not support arithmetic"),
        // remaining combos
        _ => Err("incompatible types for arithmetic"),
    }
}

/// Reserved column names — automatically provided by the engine.
const RESERVED_COLUMNS: &[(&str, &str)] =
    &[("serial", "built-in: 0-based record counter, available as {{serial}} in templates")];

/// Resolve a column value. Resolution order:
/// 1. Expression (`left + right`, `left - right`, `left * right`)
/// 2. Aggregator pattern (`source:sum`, `source:count`)
/// 3. Column reference (matches a declared column name)
/// 4. Field registry (field generator)
/// 5. Error
pub fn resolve_column(
    col_name: &str,
    value: &str,
    all_columns: &[String],
) -> Result<ColumnGen, String> {
    for (reserved, description) in RESERVED_COLUMNS {
        if col_name == *reserved {
            return Err(format!(
                "column '{col_name}' is reserved ({description}); remove it from columns"
            ));
        }
    }

    let trimmed = value.trim();
    if trimmed.is_empty() {
        return Err(format!("column '{col_name}': empty value"));
    }

    // 1. Expression: contains ` + `, ` - `, or ` * ` with spaces.
    if let Some(expr) = parse_expr(col_name, trimmed, all_columns)? {
        return Ok(expr);
    }

    // 2. Aggregator pattern.
    if let Some(aggr) = parse_aggr_spec(trimmed) {
        return Ok(aggr);
    }

    // 3. Column reference — exact match or with modifier (col:modifier).
    if trimmed != col_name && all_columns.iter().any(|d| d == trimmed) {
        return Ok(ColumnGen::Ref { source_col: trimmed.to_string(), modifier: String::new() });
    }
    if let Some(colon) = trimmed.find(':') {
        let base = &trimmed[..colon];
        let modifier = &trimmed[colon + 1..];
        if base != col_name && !modifier.is_empty() && all_columns.iter().any(|d| d == base) {
            return Ok(ColumnGen::Ref {
                source_col: base.to_string(),
                modifier: modifier.to_string(),
            });
        }
    }

    // 4. Field registry.
    resolve_field_spec(col_name, trimmed)
}

/// Try to parse `value` as `left OP right`.
/// Operators: `+`, `-`, `*` with optional surrounding whitespace.
/// `-` is ambiguous with hyphenated field names (e.g. `dns-record`),
/// so we only treat `-` as an operator when both sides resolve as
/// a valid column or field spec.
fn parse_expr(
    col_name: &str,
    value: &str,
    all_columns: &[String],
) -> Result<Option<ColumnGen>, String> {
    // Try `+` and `*` first — unambiguous (never appear in field names).
    for (ch, op) in &[('+', ExprOp::Add), ('*', ExprOp::Mul)] {
        if let Some(pos) = value.find(*ch) {
            let left_str = value[..pos].trim();
            let right_str = value[pos + 1..].trim();
            if left_str.is_empty() || right_str.is_empty() {
                continue;
            }
            return build_expr(col_name, left_str, *op, right_str, all_columns).map(Some);
        }
    }

    // `-` is ambiguous: `dns-record` vs `a - b`.
    if value.contains('-') {
        let is_field = field::parse_field_spec(value)
            .ok()
            .and_then(|(name, ..)| field::lookup(name))
            .is_some();
        if !is_field {
            for (pos, _) in value.match_indices('-') {
                let left_str = value[..pos].trim();
                let right_str = value[pos + 1..].trim();
                if left_str.is_empty() || right_str.is_empty() {
                    continue;
                }
                if is_valid_operand(left_str, all_columns)
                    && is_valid_operand(right_str, all_columns)
                {
                    return build_expr(col_name, left_str, ExprOp::Sub, right_str, all_columns)
                        .map(Some);
                }
            }
        }
    }

    Ok(None)
}

/// Build and validate an expression with type checking.
fn build_expr(
    col_name: &str,
    left_str: &str,
    op: ExprOp,
    right_str: &str,
    all_columns: &[String],
) -> Result<ColumnGen, String> {
    let (left, left_type) = resolve_operand(col_name, left_str, all_columns)?;
    let (right, right_type) = resolve_operand(col_name, right_str, all_columns)?;

    // For field operands, validate types now. Column ref types are resolved
    // in a second pass (resolve_expr_types) after all columns are parsed.
    let lt = if let ExprOperand::Col(_) = &left { FieldType::Int } else { left_type };
    let rt = if let ExprOperand::Col(_) = &right { FieldType::Int } else { right_type };

    // Preliminary check for field-to-field expressions.
    // Full validation happens in resolve_expr_types for column refs.
    let result_type =
        if matches!(&left, ExprOperand::Col(_)) || matches!(&right, ExprOperand::Col(_)) {
            ExprResultType::Int // placeholder, resolved later
        } else {
            check_expr_types(lt, op, rt).map_err(|e| format!("column '{col_name}': {e}"))?
        };

    Ok(ColumnGen::Expr { left, op, right, result_type })
}

/// Resolve the `FieldType` of a column by name. Called after all columns
/// are parsed — `resolved_columns` contains the fully parsed columns.
pub fn resolve_col_field_type(col_name: &str, columns: &[Column]) -> FieldType {
    for col in columns {
        if col.name == col_name {
            return match &col.gen {
                ColumnGen::Field { field, .. } => field_type(field.name),
                ColumnGen::Expr { result_type, .. } => match result_type {
                    ExprResultType::Int => FieldType::Int,
                    ExprResultType::Float => FieldType::Float,
                    ExprResultType::Money => FieldType::Money,
                    ExprResultType::Date => FieldType::Date,
                    ExprResultType::Timestamp => FieldType::Timestamp,
                },
                ColumnGen::Aggr { func, .. } => match func {
                    AggrFunc::Count => FieldType::Int,
                    AggrFunc::Sum => FieldType::Money,
                },
                ColumnGen::Ref { source_col, .. } => resolve_col_field_type(source_col, columns),
                ColumnGen::Literal(_) => FieldType::Text,
            };
        }
    }
    FieldType::Int
}

/// Check if a string is a valid expression operand (column or field spec)
/// without producing errors.
fn is_valid_operand(spec: &str, all_columns: &[String]) -> bool {
    if all_columns.iter().any(|c| c == spec) {
        return true;
    }
    if let Ok((name, ..)) = field::parse_field_spec(spec) {
        return field::lookup(name).is_some();
    }
    false
}

/// Resolve one operand: column reference or field spec.
fn resolve_operand(
    col_name: &str,
    spec: &str,
    all_columns: &[String],
) -> Result<(ExprOperand, FieldType), String> {
    // Column reference — type resolved later during validation.
    if all_columns.iter().any(|c| c == spec) {
        return Ok((ExprOperand::Col(spec.to_string()), FieldType::Int));
        // FieldType::Int is placeholder — real type resolved by caller
    }

    // Field spec.
    let (name, modifier, _transform, range, _ordering) =
        field::parse_field_spec(spec).map_err(|e| format!("column '{col_name}': {e}"))?;
    let f = field::lookup(name)
        .ok_or_else(|| format!("column '{col_name}': unknown field or column '{name}'"))?;
    let ft = field_type(name);
    if ft == FieldType::Text {
        return Err(format!("column '{col_name}': field '{name}' does not support arithmetic"));
    }

    Ok((ExprOperand::Field { field: f, modifier: modifier.to_string(), range }, ft))
}

/// Return column names that this `ColumnGen` depends on.
pub fn dependencies(gen: &ColumnGen) -> Vec<&str> {
    match gen {
        ColumnGen::Ref { source_col, .. } => vec![source_col.as_str()],
        ColumnGen::Aggr { source_col, group_by, .. } => {
            let mut deps = vec![source_col.as_str()];
            if let Some(g) = group_by {
                deps.push(g.as_str());
            }
            deps
        }
        ColumnGen::Expr { left, right, .. } => {
            let mut deps = Vec::new();
            if let ExprOperand::Col(name) = left {
                deps.push(name.as_str());
            }
            if let ExprOperand::Col(name) = right {
                deps.push(name.as_str());
            }
            deps
        }
        ColumnGen::Field { .. } | ColumnGen::Literal(_) => vec![],
    }
}

const AGGR_FUNCS: &[(&str, AggrFunc)] = &[("sum", AggrFunc::Sum), ("count", AggrFunc::Count)];

/// Parse `source:func` or `source:func=group` aggregator spec.
pub fn parse_aggr_spec(spec: &str) -> Option<ColumnGen> {
    let segments: Vec<&str> = spec.split(':').collect();
    for (seg_idx, seg) in segments.iter().enumerate() {
        let (func_name, group_part) = match seg.split_once('=') {
            Some((f, g)) => (f, Some(g)),
            None => (*seg, None),
        };
        let func = match AGGR_FUNCS.iter().find(|(n, _)| *n == func_name) {
            Some((_, f)) => *f,
            None => continue,
        };
        if seg_idx == 0 {
            return None;
        }
        let source = segments[..seg_idx].join(":");
        if source.is_empty() {
            return None;
        }
        let group_by = match func {
            AggrFunc::Count => Some(source.clone()),
            AggrFunc::Sum => group_part.filter(|g| !g.is_empty()).map(str::to_string),
        };
        return Some(ColumnGen::Aggr { func, source_col: source, group_by });
    }
    None
}

fn resolve_field_spec(col_name: &str, spec: &str) -> Result<ColumnGen, String> {
    if let Some(values) = spec.strip_prefix("enum:") {
        let f = field::lookup("enum").ok_or_else(|| {
            format!("column '{col_name}': internal error: 'enum' field not in registry")
        })?;
        return Ok(ColumnGen::Field {
            field: f,
            modifier: values.to_string(),
            transform: Transform::None,
            range: None,
            ordering: Ordering::None,
        });
    }

    let (name, modifier, transform, range, ordering) =
        field::parse_field_spec(spec).map_err(|e| format!("column '{col_name}': {e}"))?;

    let f = field::lookup(name)
        .ok_or_else(|| format!("column '{col_name}': unknown field or column '{name}'"))?;

    Ok(ColumnGen::Field { field: f, modifier: modifier.to_string(), transform, range, ordering })
}
