// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

use arrow::array::{
    Array, ArrayRef, BooleanBuilder, Float32Builder, Float64Builder, Int32Builder, Int64Builder,
    RecordBatch, StringBuilder, StructArray,
};
use arrow::datatypes::{DataType, Field, Schema};
use datafusion::common::Result;
use datafusion::physical_expr::PhysicalExpr;
use datafusion::physical_plan::ColumnarValue;
use std::any::Any;
use std::fmt::{Debug, Display, Formatter};
use std::sync::Arc;

/// from_json function - parses JSON strings into structured types
#[derive(Debug, Eq)]
pub struct FromJson {
    /// The JSON string input expression
    expr: Arc<dyn PhysicalExpr>,
    /// Target schema for parsing
    schema: DataType,
    /// Timezone for timestamp parsing (future use)
    timezone: String,
}

impl PartialEq for FromJson {
    fn eq(&self, other: &Self) -> bool {
        self.expr.eq(&other.expr) && self.schema == other.schema && self.timezone == other.timezone
    }
}

impl std::hash::Hash for FromJson {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.expr.hash(state);
        // Note: DataType doesn't implement Hash, so we hash its debug representation
        format!("{:?}", self.schema).hash(state);
        self.timezone.hash(state);
    }
}

impl FromJson {
    pub fn new(expr: Arc<dyn PhysicalExpr>, schema: DataType, timezone: &str) -> Self {
        Self {
            expr,
            schema,
            timezone: timezone.to_owned(),
        }
    }
}

impl Display for FromJson {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "from_json({}, schema={:?}, timezone={})",
            self.expr, self.schema, self.timezone
        )
    }
}

impl PartialEq<dyn Any> for FromJson {
    fn eq(&self, other: &dyn Any) -> bool {
        if let Some(other) = other.downcast_ref::<FromJson>() {
            self.expr.eq(&other.expr)
                && self.schema == other.schema
                && self.timezone == other.timezone
        } else {
            false
        }
    }
}

impl PhysicalExpr for FromJson {
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn fmt_sql(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(self, f)
    }

    fn data_type(&self, _: &Schema) -> Result<DataType> {
        Ok(self.schema.clone())
    }

    fn nullable(&self, _input_schema: &Schema) -> Result<bool> {
        // Always nullable - parse errors return null in PERMISSIVE mode
        Ok(true)
    }

    fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
        let input = self.expr.evaluate(batch)?.into_array(batch.num_rows())?;
        Ok(ColumnarValue::Array(json_string_to_struct(
            &input,
            &self.schema,
        )?))
    }

    fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
        vec![&self.expr]
    }

    fn with_new_children(
        self: Arc<Self>,
        children: Vec<Arc<dyn PhysicalExpr>>,
    ) -> Result<Arc<dyn PhysicalExpr>> {
        assert!(children.len() == 1);
        Ok(Arc::new(Self::new(
            Arc::clone(&children[0]),
            self.schema.clone(),
            &self.timezone,
        )))
    }
}

/// Parse JSON string array into struct array
fn json_string_to_struct(arr: &Arc<dyn Array>, schema: &DataType) -> Result<ArrayRef> {
    use arrow::array::StringArray;
    use arrow::buffer::NullBuffer;

    let string_array = arr.as_any().downcast_ref::<StringArray>().ok_or_else(|| {
        datafusion::common::DataFusionError::Execution("from_json expects string input".to_string())
    })?;

    let DataType::Struct(fields) = schema else {
        return Err(datafusion::common::DataFusionError::Execution(
            "from_json requires struct schema".to_string(),
        ));
    };

    let num_rows = string_array.len();
    let mut field_builders = create_field_builders(fields, num_rows)?;
    let mut struct_nulls = vec![true; num_rows];
    for (row_idx, struct_null) in struct_nulls.iter_mut().enumerate() {
        if string_array.is_null(row_idx) {
            // Null input -> null struct
            *struct_null = false;
            append_null_to_all_builders(&mut field_builders);
        } else {
            let json_str = string_array.value(row_idx);

            // Parse JSON (PERMISSIVE mode: return null fields on error)
            match serde_json::from_str::<serde_json::Value>(json_str) {
                Ok(json_value) => {
                    if let serde_json::Value::Object(obj) = json_value {
                        // Struct is not null, extract each field
                        *struct_null = true;
                        for (field, builder) in fields.iter().zip(field_builders.iter_mut()) {
                            let field_value = obj.get(field.name());
                            append_field_value(builder, field, field_value)?;
                        }
                    } else {
                        // Not an object -> struct with null fields
                        *struct_null = true;
                        append_null_to_all_builders(&mut field_builders);
                    }
                }
                Err(_) => {
                    // Parse error -> struct with null fields (PERMISSIVE mode)
                    *struct_null = true;
                    append_null_to_all_builders(&mut field_builders);
                }
            }
        }
    }

    let arrays: Vec<ArrayRef> = field_builders
        .into_iter()
        .map(finish_builder)
        .collect::<Result<Vec<_>>>()?;
    let null_buffer = NullBuffer::from(struct_nulls);
    Ok(Arc::new(StructArray::new(
        fields.clone(),
        arrays,
        Some(null_buffer),
    )))
}

/// Builder enum for different data types
enum FieldBuilder {
    Int32(Int32Builder),
    Int64(Int64Builder),
    Float32(Float32Builder),
    Float64(Float64Builder),
    Boolean(BooleanBuilder),
    String(StringBuilder),
    Struct {
        fields: arrow::datatypes::Fields,
        builders: Vec<FieldBuilder>,
        null_buffer: Vec<bool>,
    },
}

fn create_field_builders(
    fields: &arrow::datatypes::Fields,
    capacity: usize,
) -> Result<Vec<FieldBuilder>> {
    fields
        .iter()
        .map(|field| match field.data_type() {
            DataType::Int32 => Ok(FieldBuilder::Int32(Int32Builder::with_capacity(capacity))),
            DataType::Int64 => Ok(FieldBuilder::Int64(Int64Builder::with_capacity(capacity))),
            DataType::Float32 => Ok(FieldBuilder::Float32(Float32Builder::with_capacity(
                capacity,
            ))),
            DataType::Float64 => Ok(FieldBuilder::Float64(Float64Builder::with_capacity(
                capacity,
            ))),
            DataType::Boolean => Ok(FieldBuilder::Boolean(BooleanBuilder::with_capacity(
                capacity,
            ))),
            DataType::Utf8 => Ok(FieldBuilder::String(StringBuilder::with_capacity(
                capacity,
                capacity * 16,
            ))),
            DataType::Struct(nested_fields) => {
                let nested_builders = create_field_builders(nested_fields, capacity)?;
                Ok(FieldBuilder::Struct {
                    fields: nested_fields.clone(),
                    builders: nested_builders,
                    null_buffer: Vec::with_capacity(capacity),
                })
            }
            dt => Err(datafusion::common::DataFusionError::Execution(format!(
                "Unsupported field type in from_json: {:?}",
                dt
            ))),
        })
        .collect()
}

fn append_null_to_all_builders(builders: &mut [FieldBuilder]) {
    for builder in builders {
        match builder {
            FieldBuilder::Int32(b) => b.append_null(),
            FieldBuilder::Int64(b) => b.append_null(),
            FieldBuilder::Float32(b) => b.append_null(),
            FieldBuilder::Float64(b) => b.append_null(),
            FieldBuilder::Boolean(b) => b.append_null(),
            FieldBuilder::String(b) => b.append_null(),
            FieldBuilder::Struct {
                builders: nested_builders,
                null_buffer,
                ..
            } => {
                // Append null to nested struct
                null_buffer.push(false);
                append_null_to_all_builders(nested_builders);
            }
        }
    }
}

fn append_field_value(
    builder: &mut FieldBuilder,
    field: &Field,
    json_value: Option<&serde_json::Value>,
) -> Result<()> {
    use serde_json::Value;

    let value = match json_value {
        Some(Value::Null) | None => {
            // Missing field or explicit null -> append null
            match builder {
                FieldBuilder::Int32(b) => b.append_null(),
                FieldBuilder::Int64(b) => b.append_null(),
                FieldBuilder::Float32(b) => b.append_null(),
                FieldBuilder::Float64(b) => b.append_null(),
                FieldBuilder::Boolean(b) => b.append_null(),
                FieldBuilder::String(b) => b.append_null(),
                FieldBuilder::Struct {
                    builders: nested_builders,
                    null_buffer,
                    ..
                } => {
                    null_buffer.push(false);
                    append_null_to_all_builders(nested_builders);
                }
            }
            return Ok(());
        }
        Some(v) => v,
    };

    match (builder, field.data_type()) {
        (FieldBuilder::Int32(b), DataType::Int32) => {
            if let Some(i) = value.as_i64() {
                if i >= i32::MIN as i64 && i <= i32::MAX as i64 {
                    b.append_value(i as i32);
                } else {
                    b.append_null(); // Overflow
                }
            } else {
                b.append_null(); // Type mismatch
            }
        }
        (FieldBuilder::Int64(b), DataType::Int64) => {
            if let Some(i) = value.as_i64() {
                b.append_value(i);
            } else {
                b.append_null();
            }
        }
        (FieldBuilder::Float32(b), DataType::Float32) => {
            if let Some(f) = value.as_f64() {
                b.append_value(f as f32);
            } else {
                b.append_null();
            }
        }
        (FieldBuilder::Float64(b), DataType::Float64) => {
            if let Some(f) = value.as_f64() {
                b.append_value(f);
            } else {
                b.append_null();
            }
        }
        (FieldBuilder::Boolean(b), DataType::Boolean) => {
            if let Some(bool_val) = value.as_bool() {
                b.append_value(bool_val);
            } else {
                b.append_null();
            }
        }
        (FieldBuilder::String(b), DataType::Utf8) => {
            if let Some(s) = value.as_str() {
                b.append_value(s);
            } else {
                // Stringify non-string values
                b.append_value(value.to_string());
            }
        }
        (
            FieldBuilder::Struct {
                fields: nested_fields,
                builders: nested_builders,
                null_buffer,
            },
            DataType::Struct(_),
        ) => {
            // Handle nested struct
            if let Some(obj) = value.as_object() {
                // Non-null nested struct
                null_buffer.push(true);
                for (nested_field, nested_builder) in
                    nested_fields.iter().zip(nested_builders.iter_mut())
                {
                    let nested_value = obj.get(nested_field.name());
                    append_field_value(nested_builder, nested_field, nested_value)?;
                }
            } else {
                // Not an object -> null nested struct
                null_buffer.push(false);
                append_null_to_all_builders(nested_builders);
            }
        }
        _ => {
            return Err(datafusion::common::DataFusionError::Execution(
                "Type mismatch in from_json".to_string(),
            ));
        }
    }

    Ok(())
}

fn finish_builder(builder: FieldBuilder) -> Result<ArrayRef> {
    Ok(match builder {
        FieldBuilder::Int32(mut b) => Arc::new(b.finish()),
        FieldBuilder::Int64(mut b) => Arc::new(b.finish()),
        FieldBuilder::Float32(mut b) => Arc::new(b.finish()),
        FieldBuilder::Float64(mut b) => Arc::new(b.finish()),
        FieldBuilder::Boolean(mut b) => Arc::new(b.finish()),
        FieldBuilder::String(mut b) => Arc::new(b.finish()),
        FieldBuilder::Struct {
            fields,
            builders,
            null_buffer,
        } => {
            let nested_arrays: Vec<ArrayRef> = builders
                .into_iter()
                .map(finish_builder)
                .collect::<Result<Vec<_>>>()?;
            let null_buf = arrow::buffer::NullBuffer::from(null_buffer);
            Arc::new(StructArray::new(fields, nested_arrays, Some(null_buf)))
        }
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use arrow::array::{Int32Array, StringArray};
    use arrow::datatypes::Fields;

    #[test]
    fn test_simple_struct() -> Result<()> {
        let schema = DataType::Struct(Fields::from(vec![
            Field::new("a", DataType::Int32, true),
            Field::new("b", DataType::Utf8, true),
        ]));

        let input: Arc<dyn Array> = Arc::new(StringArray::from(vec![
            Some(r#"{"a": 123, "b": "hello"}"#),
            Some(r#"{"a": 456}"#),
            Some(r#"invalid json"#),
            None,
        ]));

        let result = json_string_to_struct(&input, &schema)?;
        let struct_array = result.as_any().downcast_ref::<StructArray>().unwrap();

        assert_eq!(struct_array.len(), 4);

        // First row
        let a_array = struct_array
            .column(0)
            .as_any()
            .downcast_ref::<Int32Array>()
            .unwrap();
        assert_eq!(a_array.value(0), 123);
        let b_array = struct_array
            .column(1)
            .as_any()
            .downcast_ref::<StringArray>()
            .unwrap();
        assert_eq!(b_array.value(0), "hello");

        // Second row (missing field b)
        assert_eq!(a_array.value(1), 456);
        assert!(b_array.is_null(1));

        // Third row (parse error -> struct NOT null, all fields null)
        assert!(!struct_array.is_null(2), "Struct should not be null");
        assert!(a_array.is_null(2));
        assert!(b_array.is_null(2));

        // Fourth row (null input -> struct IS null)
        assert!(struct_array.is_null(3), "Struct itself should be null");

        Ok(())
    }

    #[test]
    fn test_all_primitive_types() -> Result<()> {
        let schema = DataType::Struct(Fields::from(vec![
            Field::new("i32", DataType::Int32, true),
            Field::new("i64", DataType::Int64, true),
            Field::new("f32", DataType::Float32, true),
            Field::new("f64", DataType::Float64, true),
            Field::new("bool", DataType::Boolean, true),
            Field::new("str", DataType::Utf8, true),
        ]));

        let input: Arc<dyn Array> = Arc::new(StringArray::from(vec![Some(
            r#"{"i32":123,"i64":9999999999,"f32":1.5,"f64":2.5,"bool":true,"str":"test"}"#,
        )]));

        let result = json_string_to_struct(&input, &schema)?;
        let struct_array = result.as_any().downcast_ref::<StructArray>().unwrap();

        assert_eq!(struct_array.len(), 1);

        // Verify all types
        let i32_array = struct_array
            .column(0)
            .as_any()
            .downcast_ref::<Int32Array>()
            .unwrap();
        assert_eq!(i32_array.value(0), 123);

        let i64_array = struct_array
            .column(1)
            .as_any()
            .downcast_ref::<arrow::array::Int64Array>()
            .unwrap();
        assert_eq!(i64_array.value(0), 9999999999);

        let f32_array = struct_array
            .column(2)
            .as_any()
            .downcast_ref::<arrow::array::Float32Array>()
            .unwrap();
        assert_eq!(f32_array.value(0), 1.5);

        let f64_array = struct_array
            .column(3)
            .as_any()
            .downcast_ref::<arrow::array::Float64Array>()
            .unwrap();
        assert_eq!(f64_array.value(0), 2.5);

        let bool_array = struct_array
            .column(4)
            .as_any()
            .downcast_ref::<arrow::array::BooleanArray>()
            .unwrap();
        assert!(bool_array.value(0));

        let str_array = struct_array
            .column(5)
            .as_any()
            .downcast_ref::<StringArray>()
            .unwrap();
        assert_eq!(str_array.value(0), "test");

        Ok(())
    }

    #[test]
    fn test_empty_and_null_json() -> Result<()> {
        let schema = DataType::Struct(Fields::from(vec![
            Field::new("a", DataType::Int32, true),
            Field::new("b", DataType::Utf8, true),
        ]));

        let input: Arc<dyn Array> = Arc::new(StringArray::from(vec![
            Some(r#"{}"#),   // Empty object
            Some(r#"null"#), // JSON null
            Some(r#"[]"#),   // Array (not object)
            Some(r#"123"#),  // Number (not object)
        ]));

        let result = json_string_to_struct(&input, &schema)?;
        let struct_array = result.as_any().downcast_ref::<StructArray>().unwrap();

        assert_eq!(struct_array.len(), 4);

        let a_array = struct_array
            .column(0)
            .as_any()
            .downcast_ref::<Int32Array>()
            .unwrap();
        let b_array = struct_array
            .column(1)
            .as_any()
            .downcast_ref::<StringArray>()
            .unwrap();

        // All rows should have non-null structs with null field values
        for i in 0..4 {
            assert!(
                !struct_array.is_null(i),
                "Row {} struct should not be null",
                i
            );
            assert!(a_array.is_null(i), "Row {} field a should be null", i);
            assert!(b_array.is_null(i), "Row {} field b should be null", i);
        }

        Ok(())
    }

    #[test]
    fn test_nested_struct() -> Result<()> {
        let schema = DataType::Struct(Fields::from(vec![
            Field::new(
                "outer",
                DataType::Struct(Fields::from(vec![
                    Field::new("inner_a", DataType::Int32, true),
                    Field::new("inner_b", DataType::Utf8, true),
                ])),
                true,
            ),
            Field::new("top_level", DataType::Int32, true),
        ]));

        let input: Arc<dyn Array> = Arc::new(StringArray::from(vec![
            Some(r#"{"outer":{"inner_a":123,"inner_b":"hello"},"top_level":999}"#),
            Some(r#"{"outer":{"inner_a":456},"top_level":888}"#), // Missing nested field
            Some(r#"{"outer":null,"top_level":777}"#),            // Null nested struct
            Some(r#"{"top_level":666}"#),                         // Missing nested struct
        ]));

        let result = json_string_to_struct(&input, &schema)?;
        let struct_array = result.as_any().downcast_ref::<StructArray>().unwrap();

        assert_eq!(struct_array.len(), 4);

        // Check outer struct
        let outer_array = struct_array
            .column(0)
            .as_any()
            .downcast_ref::<StructArray>()
            .unwrap();
        let top_level_array = struct_array
            .column(1)
            .as_any()
            .downcast_ref::<Int32Array>()
            .unwrap();

        // Row 0: Valid nested struct
        assert!(!outer_array.is_null(0), "Nested struct should not be null");
        let inner_a_array = outer_array
            .column(0)
            .as_any()
            .downcast_ref::<Int32Array>()
            .unwrap();
        let inner_b_array = outer_array
            .column(1)
            .as_any()
            .downcast_ref::<StringArray>()
            .unwrap();
        assert_eq!(inner_a_array.value(0), 123);
        assert_eq!(inner_b_array.value(0), "hello");
        assert_eq!(top_level_array.value(0), 999);

        // Row 1: Missing nested field
        assert!(!outer_array.is_null(1));
        assert_eq!(inner_a_array.value(1), 456);
        assert!(inner_b_array.is_null(1));
        assert_eq!(top_level_array.value(1), 888);

        // Row 2: Null nested struct
        assert!(outer_array.is_null(2), "Nested struct should be null");
        assert_eq!(top_level_array.value(2), 777);

        // Row 3: Missing nested struct
        assert!(outer_array.is_null(3), "Nested struct should be null");
        assert_eq!(top_level_array.value(3), 666);

        Ok(())
    }
}
