use std::hash::{DefaultHasher, Hash, Hasher};
// use autark_client::{OnceFrame, Result};
use autark::prelude::*;

use arrow::array::StringArray;
use arrow::array::record_batch;
use autark_reader::readers::arrow::ArrowReader;
fn hash_of_t<T: Hash>(x: &T) -> Option<u64> {
    let mut hasher = DefaultHasher::new();
    x.hash(&mut hasher);

    Some(hasher.finish())
}

#[test]
fn t1() -> AutarkResult<()> {
    let input = record_batch!(
        ("a", Int32, [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]),
        (
            "b",
            Float32,
            [0.1, 0.2, 0.4, 0.8, 1.6, 3.2, 6.4, 12.8, 25.6, 51.2, 102.4]
        ),
        (
            "c",
            Utf8,
            [
                "BOB", "ALICE", "BOB", "BOB", "ALICE", "ALICE", "ALICE", "BOB", "ALICE", "BOB",
                "ALICE"
            ]
        )
    )?;
    let reader = ArrowReader::new(input);

    let mut frame = OnceFrame::new(reader);
    let p = Program::new();

    p.columns(None, ColumnSelector::All)?
        .columns(None, ColumnSelector::Names(vec!["a".to_string()]))?
        .reduce(ReduceKind::Sum)?
        .alias("a_sum", None)?; // Shall automatically detect result is 0-d scalar and not return a frame.

    p.columns(None, ColumnSelector::All)?
        .columns(None, ColumnSelector::Names(vec!["b".to_string()]))?
        .reduce(ReduceKind::Sum)?
        .alias("b_sum", None)?; // Shall automatically detect result is 0-d scalar and not return a frame.

    p.columns(None, ColumnSelector::All)?
        .columns(None, ColumnSelector::Names(vec!["a".to_string()]))?
        .rolling(4)?
        .reduce(ReduceKind::Mean)?
        .alias("rolling_mean", None)?;

    p.columns(None, ColumnSelector::All)?
        .columns(None, ColumnSelector::Names(vec!["c".to_string()]))?
        .group_reduce(
            Some(p.columns(None, ColumnSelector::Names(vec!["c".to_string()]))?),
            vec![ReduceKind::Count],
        )?
        .alias(
            "group_by_name",
            Some(frame.schema_of_columns(None, &["c", "a"])?),
        )?;

    let realized = frame.realize(p, None)?;
    dbg!(hash_of_t(&realized));

    // IMPORTANT: If underlying transformation is mutated, the Hash ought to be recomputed.
    // assert_eq!(hash_of_t(&realized), Some(4566204555854787833));

    Ok(())
}

#[test]
fn group_by_without_reduce_returns_grouped_output() -> AutarkResult<()> {
    let input = record_batch!(
        ("city", Utf8, ["A", "B", "A", "C", "B"]),
        ("value", Int32, [1, 2, 3, 4, 5])
    )?;
    let reader = ArrowReader::new(input);

    let mut frame = OnceFrame::new(reader);
    let p = Program::new();

    p.columns(None, ColumnSelector::Names(vec!["city".to_string()]))?
        .group_by(Some(
            p.columns(None, ColumnSelector::Names(vec!["city".to_string()]))?,
        ))?
        .alias("cities", None)?;

    let realized = frame.realize(p, None)?;
    let groups = match realized.output().0.get("cities").unwrap() {
        autark::mpera::programoutput::OutputValue::Groups(groups) => groups,
        _ => panic!("expected grouped output"),
    };

    assert_eq!(
        groups.keys().cloned().collect::<Vec<_>>(),
        vec![
            "A\u{1f}".to_string(),
            "B\u{1f}".to_string(),
            "C\u{1f}".to_string()
        ]
    );

    let batch = groups.get("A\u{1f}").unwrap();
    let cities = batch
        .column(0)
        .as_any()
        .downcast_ref::<StringArray>()
        .unwrap();
    assert_eq!(
        cities.iter().collect::<Vec<_>>(),
        vec![Some("A"), Some("A")]
    );

    Ok(())
}

#[test]
fn program_ir_is_json() -> AutarkResult<()> {
    let p = Program::new();
    let ir = p
        .columns(None, ColumnSelector::Names(vec!["a".to_string()]))?
        .alias("out", None)?
        .ir()?;
    let value: serde_json::Value = serde_json::from_str(&ir)?;

    assert_eq!(value["root"].as_u64().is_some(), true);
    assert_eq!(value["op_pool"].is_array(), true);
    assert_eq!(value["metadata"].is_object(), true);
    assert_eq!(value["signature"].is_object(), true);

    Ok(())
}
