#[cfg(feature = "arrow")]
use crate::arrow::convert_table;
use crate::client::{KustoClient, QueryKind};

use crate::error::{Error, Result};
use crate::models::{DataTable, QueryBody, TableFragmentType, TableKind, TableV1, V2QueryResult};
use crate::operations::async_deserializer;
use crate::prelude::ClientRequestProperties;
#[cfg(feature = "arrow")]
use arrow::record_batch::RecordBatch;
use async_convert::TryFrom;
use azure_core::error::Error as CoreError;
use azure_core::headers::Headers;
use azure_core::prelude::*;
use azure_core::{CustomHeaders, Method, Request, Response as HttpResponse, Response};
use futures::future::BoxFuture;
use futures::{Stream, TryFutureExt, TryStreamExt};
use serde::{Deserialize, Serialize};
use std::future::IntoFuture;
use std::io::ErrorKind;
use std::sync::Arc;

type QueryRun = BoxFuture<'static, Result<KustoResponse>>;
type V1QueryRun = BoxFuture<'static, Result<KustoResponseDataSetV1>>;
type V2QueryRun = BoxFuture<'static, Result<KustoResponseDataSetV2>>;

#[derive(Debug, Clone, derive_builder::Builder)]
#[builder(setter(into, prefix = "with"))]
pub struct QueryRunner {
    client: KustoClient,
    database: String,
    query: String,
    kind: QueryKind,
    client_request_properties: Option<ClientRequestProperties>,
    default_headers: Arc<Headers>,
}
pub struct V1QueryRunner(pub QueryRunner);

pub struct V2QueryRunner(pub QueryRunner);

impl V2QueryRunner {
    pub async fn into_stream(self) -> Result<impl Stream<Item = Result<V2QueryResult>>> {
        let V2QueryRunner(query_runner) = self;
        query_runner.into_stream().await
    }
}

impl QueryRunner {
    async fn into_response(self) -> Result<Response> {
        let url = match self.kind {
            QueryKind::Management => self.client.management_url(),
            QueryKind::Query => self.client.query_url(),
        };
        let mut request = Request::new(url.parse().map_err(CoreError::from)?, Method::Post);

        let mut context = Context::new();
        let mut headers = self.default_headers.as_ref().clone();

        if let Some(client_request_properties) = &self.client_request_properties {
            if let Some(client_request_id) = &client_request_properties.client_request_id {
                headers.insert("x-ms-client-request-id", client_request_id);
            }

            if let Some(application) = &client_request_properties.application {
                headers.insert("x-ms-app", application);
            }
        }

        context.insert(CustomHeaders::from(headers));

        let body = QueryBody {
            db: self.database,
            csl: self.query,
            properties: self.client_request_properties,
        };

        let bytes = bytes::Bytes::from(serde_json::to_string(&body)?);
        request.set_body(bytes);

        let response = self
            .client
            .pipeline()
            .send(&mut context, &mut request)
            .await?;
        Ok(response)
    }

    pub async fn into_stream(self) -> Result<impl Stream<Item = Result<V2QueryResult>>> {
        if self.kind != QueryKind::Query {
            return Err(Error::UnsupportedOperation(
                "Progressive streaming is only supported for queries".to_string(),
            ));
        }

        let response = self.into_response().await?;
        let (_status_code, _header_map, pinned_stream) = response.deconstruct();
        let reader = pinned_stream
            .map_err(|e| std::io::Error::new(ErrorKind::Other, e))
            .into_async_read();

        Ok(async_deserializer::iter_results(reader).map_err(Error::from))
    }
}

impl IntoFuture for V1QueryRunner {
    type Output = Result<KustoResponseDataSetV1>;
    type IntoFuture = V1QueryRun;

    fn into_future(self) -> V1QueryRun {
        Box::pin(async {
            let V1QueryRunner(query_runner) = self;
            let future = query_runner.into_future().await?;
            Ok(
                std::convert::TryInto::try_into(future).expect("Unexpected conversion error from KustoResponse to KustoResponseDataSetV1 - please report this issue to the Kusto team")
            )
        })
    }
}

impl IntoFuture for V2QueryRunner {
    type Output = Result<KustoResponseDataSetV2>;
    type IntoFuture = V2QueryRun;

    fn into_future(self) -> V2QueryRun {
        Box::pin(async {
            let V2QueryRunner(query_runner) = self;
            let future = query_runner.into_future().await?;
            Ok(
                std::convert::TryInto::try_into(future).expect("Unexpected conversion error from KustoResponse to KustoResponseDataSetV2 - please report this issue to the Kusto team")
            )
        })
    }
}

impl IntoFuture for QueryRunner {
    type Output = Result<KustoResponse>;
    type IntoFuture = QueryRun;

    fn into_future(self) -> QueryRun {
        let this = self.clone();

        Box::pin(async move {
            let response = self.into_response().await?;

            Ok(match this.kind {
                QueryKind::Management => {
                    <KustoResponseDataSetV1 as TryFrom<HttpResponse>>::try_from(response)
                        .map_ok(KustoResponse::V1)
                        .await?
                }
                QueryKind::Query => {
                    <KustoResponseDataSetV2 as TryFrom<HttpResponse>>::try_from(response)
                        .map_ok(KustoResponse::V2)
                        .await?
                }
            })
        })
    }
}

/// A Kusto query response.
#[derive(Debug, Clone)]
pub enum KustoResponse {
    /// V1 Response - represents management queries, and old V1 data queries.
    V1(KustoResponseDataSetV1),
    /// V2 Response - represents new V2 data queries.
    V2(KustoResponseDataSetV2),
}

/// The top level response from a Kusto query.
#[derive(Debug, Clone)]
pub struct KustoResponseDataSetV2 {
    /// All of the raw results in the response.
    pub results: Vec<V2QueryResult>,
}

impl std::convert::TryFrom<KustoResponse> for KustoResponseDataSetV2 {
    type Error = Error;

    fn try_from(value: KustoResponse) -> Result<Self> {
        match value {
            KustoResponse::V2(v2) => Ok(v2),
            _ => Err(Error::ConversionError("KustoResponseDataSetV2".to_string())),
        }
    }
}

impl std::convert::TryFrom<KustoResponse> for KustoResponseDataSetV1 {
    type Error = Error;

    fn try_from(value: KustoResponse) -> Result<Self> {
        match value {
            KustoResponse::V1(v1) => Ok(v1),
            _ => Err(Error::ConversionError("KustoResponseDataSetV2".to_string())),
        }
    }
}

struct KustoResponseDataSetV2TableIterator<T: Iterator<Item = V2QueryResult>> {
    tables: T,
    finished: bool,
}

impl<T: Iterator<Item = V2QueryResult>> KustoResponseDataSetV2TableIterator<T> {
    fn new(tables: T) -> Self {
        Self {
            tables,
            finished: false,
        }
    }
}

impl<T: Iterator<Item = V2QueryResult>> Iterator for KustoResponseDataSetV2TableIterator<T> {
    type Item = DataTable;

    fn next(&mut self) -> Option<Self::Item> {
        if self.finished {
            return None;
        }
        let next_table = self.tables.find_map(|t| match t {
            V2QueryResult::DataTable(_) | V2QueryResult::TableHeader(_) => Some(t),
            _ => None,
        });

        if let Some(V2QueryResult::DataTable(t)) = next_table {
            return Some(t);
        }

        let mut table = DataTable {
            table_id: 0,
            table_name: "".to_string(),
            table_kind: TableKind::Unknown,
            columns: vec![],
            rows: vec![],
        };

        if let Some(V2QueryResult::TableHeader(header)) = next_table {
            table.table_id = header.table_id;
            table.table_kind = header.table_kind;
            table.table_name = header.table_name;
            table.columns = header.columns;
        } else {
            self.finished = true;
            return None;
        }

        let mut finished_table = false;

        for result in &mut self.tables {
            match result {
                V2QueryResult::TableFragment(fragment) => {
                    assert_eq!(fragment.table_id, table.table_id);
                    match fragment.table_fragment_type {
                        TableFragmentType::DataAppend => table.rows.extend(fragment.rows),
                        TableFragmentType::DataReplace => table.rows = fragment.rows,
                    };
                }
                V2QueryResult::TableProgress(progress) => {
                    assert_eq!(progress.table_id, table.table_id);
                }
                V2QueryResult::TableCompletion(completion) => {
                    assert_eq!(completion.table_id, table.table_id);
                    assert_eq!(
                        completion.row_count,
                        TryInto::<i32>::try_into(table.rows.len()).expect("Row count overflow")
                    );
                    finished_table = true;
                    break;
                }
                _ => unreachable!("Unexpected result type"),
            }
        }

        if finished_table {
            Some(table)
        } else {
            None
        }
    }
}

impl KustoResponseDataSetV2 {
    /// Count of the number of the raw results in the response.
    /// This, in addition to tables, includes headers and other non-table results.
    /// # Example
    /// ```rust
    /// use azure_kusto_data::models::*;
    /// use azure_kusto_data::prelude::{DataTable, KustoResponseDataSetV2};
    ///
    /// let data_set = KustoResponseDataSetV2 {
    ///    results: vec![
    ///         V2QueryResult::DataSetHeader(DataSetHeader {is_progressive: false,version: "".to_string()}),
    ///         V2QueryResult::DataTable(DataTable {
    ///         table_id: 0,
    ///         table_name: "table_1".to_string(),
    ///         table_kind: TableKind::PrimaryResult,
    ///         columns: vec![],
    ///         rows: vec![],
    ///         }),
    ///     ], };
    ///
    /// assert_eq!(data_set.raw_results_count(), 2);
    /// ```
    #[must_use]
    pub fn raw_results_count(&self) -> usize {
        self.results.len()
    }

    /// Iterates over the tables in the response.
    /// If the query is progressive, it will combine the table parts into a single table.
    ///
    /// This method does not consume the response, so it can be called multiple times.
    /// [Use into_parsed_data_tables](#method.into_parsed_data_tables) to consume the response and reduce memory usage.
    /// # Example
    /// ```rust
    /// use azure_kusto_data::models::*;
    /// use azure_kusto_data::prelude::{DataTable, KustoResponseDataSetV2};
    ///
    ///let data_set = KustoResponseDataSetV2 {
    ///results: vec![
    ///    V2QueryResult::DataSetHeader(DataSetHeader {is_progressive: false,version: "".to_string()}),
    ///    V2QueryResult::DataTable(DataTable {
    ///        table_id: 0,
    ///        table_name: "table_1".to_string(),
    ///        table_kind: TableKind::QueryCompletionInformation,
    ///        columns: vec![],
    ///        rows: vec![],
    ///    }),
    ///    V2QueryResult::TableHeader(TableHeader {
    ///        table_id: 1,
    ///        table_name: "table_2".to_string(),
    ///        table_kind: TableKind::PrimaryResult,
    ///        columns: vec![],
    ///    }),
    ///    V2QueryResult::TableCompletion(TableCompletion {
    ///        table_id: 1,
    ///        row_count: 0,
    ///    }),
    ///],
    ///};
    /// let mut results = vec![];
    /// for table in data_set.parsed_data_tables() {
    ///    results.push(format!("{} - {}", table.table_id, table.table_name));
    /// }
    ///
    /// assert_eq!(results, vec!["0 - table_1", "1 - table_2"]);
    /// ```
    pub fn parsed_data_tables(&self) -> impl Iterator<Item = DataTable> + '_ {
        KustoResponseDataSetV2TableIterator::new(self.results.iter().cloned())
    }

    /// Iterates over the tables in the response, yielding only the primary tables.
    /// If the query is progressive, it will combine the table parts into a single table.
    ///
    /// This method does not consume the response, so it can be called multiple times.
    /// [Use into_primary_results](#method.into_primary_results) to consume the response and reduce memory usage.
    /// # Example
    /// ```rust
    /// use azure_kusto_data::models::*;
    /// use azure_kusto_data::prelude::{DataTable, KustoResponseDataSetV2};
    ///
    ///let data_set = KustoResponseDataSetV2 {
    ///results: vec![
    ///    V2QueryResult::DataSetHeader(DataSetHeader {is_progressive: false,version: "".to_string()}),
    ///    V2QueryResult::DataTable(DataTable {
    ///        table_id: 0,
    ///        table_name: "table_1".to_string(),
    ///        table_kind: TableKind::QueryCompletionInformation,
    ///        columns: vec![],
    ///        rows: vec![],
    ///    }),
    ///    V2QueryResult::TableHeader(TableHeader {
    ///        table_id: 1,
    ///        table_name: "table_2".to_string(),
    ///        table_kind: TableKind::PrimaryResult,
    ///        columns: vec![],
    ///    }),
    ///    V2QueryResult::TableCompletion(TableCompletion {
    ///        table_id: 1,
    ///        row_count: 0,
    ///    }),
    ///],
    ///};
    /// let mut results = vec![];
    /// for table in data_set.primary_results() {
    ///    results.push(format!("{} - {}", table.table_id, table.table_name));
    /// }
    ///
    /// assert_eq!(results, vec!["1 - table_2"]);
    /// ```
    /// Consumes the response into an iterator over all PrimaryResult tables within the response dataset
    pub fn primary_results(&self) -> impl Iterator<Item = DataTable> + '_ {
        self.parsed_data_tables()
            .filter(|t| t.table_kind == TableKind::PrimaryResult)
    }

    /// Iterates over the tables in the response, and converts them into `arrow` `Batches`
    /// If the query is progressive, it will combine the table parts into a single table.
    ///
    /// This method does not consume the response, so it can be called multiple times.
    /// [Use into_primary_results](#method.into_primary_results) to consume the response and reduce memory usage.
    /// # Example
    /// ```rust
    /// use serde_json::Value;
    /// use azure_kusto_data::models::*;
    /// use azure_kusto_data::prelude::{DataTable, KustoResponseDataSetV2};
    ///
    ///let data_set = KustoResponseDataSetV2 {
    ///results: vec![
    ///    V2QueryResult::DataSetHeader(DataSetHeader {is_progressive: false,version: "".to_string()}),
    ///    V2QueryResult::DataTable(DataTable {
    ///        table_id: 0,
    ///        table_name: "table_1".to_string(),
    ///        table_kind: TableKind::PrimaryResult,
    ///        columns: vec![Column{column_name: "col1".to_string(), column_type: ColumnType::Long}],
    ///        rows: vec![Value::Array(vec![Value::from(3u64)])],
    ///    }),
    ///    V2QueryResult::TableHeader(TableHeader {
    ///        table_id: 1,
    ///        table_name: "table_2".to_string(),
    ///        table_kind: TableKind::PrimaryResult,
    ///        columns: vec![Column{column_name: "col1".to_string(), column_type: ColumnType::String}],
    ///    }),
    ///    V2QueryResult::TableFragment(TableFragment {
    ///       table_id: 1,
    ///       rows: vec![Value::Array(vec![Value::from("first")]), Value::Array(vec![Value::from("second")])],
    ///       field_count: Some(1),
    ///       table_fragment_type: TableFragmentType::DataAppend,
    ///     }),
    ///    V2QueryResult::TableCompletion(TableCompletion {
    ///        table_id: 1,
    ///        row_count: 2,
    ///    }),
    ///],
    ///};
    /// let mut results = vec![];
    /// for batch in data_set.record_batches() {
    ///    results.push(batch.map(|b| b.num_rows()).unwrap_or(0));
    /// }
    ///
    /// assert_eq!(results, vec![1, 2]);
    /// ```
    /// Consumes the response into an iterator over all PrimaryResult tables within the response dataset
    #[cfg(feature = "arrow")]
    pub fn record_batches(&self) -> impl Iterator<Item = Result<RecordBatch>> + '_ {
        self.primary_results().map(convert_table)
    }

    /// Consuming version for [parse_data_tables](#method.parse_data_tables).
    pub fn into_parsed_data_tables(self) -> impl Iterator<Item = DataTable> {
        KustoResponseDataSetV2TableIterator::new(self.results.into_iter())
    }

    /// Consuming version for [primary_results](#method.primary_results).
    pub fn into_primary_results(self) -> impl Iterator<Item = DataTable> {
        self.into_parsed_data_tables()
            .filter(|t| t.table_kind == TableKind::PrimaryResult)
    }

    #[cfg(feature = "arrow")]
    /// Consuming version for [record_batches](#method.record_batches).
    pub fn into_record_batches(self) -> impl Iterator<Item = Result<RecordBatch>> {
        self.into_primary_results().map(convert_table)
    }
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
#[serde(rename_all = "PascalCase")]
/// The header of a Kusto response dataset for v1. Contains a list of tables.
pub struct KustoResponseDataSetV1 {
    /// The list of tables in the dataset.
    pub tables: Vec<TableV1>,
}

impl KustoResponseDataSetV1 {
    #[must_use]
    /// Count the number of tables in the dataset.
    /// # Example
    /// ```rust
    /// use azure_kusto_data::models::TableV1;
    /// use azure_kusto_data::prelude::KustoResponseDataSetV1;
    /// let dataset = KustoResponseDataSetV1 {
    ///    tables: vec![
    ///       TableV1 {
    ///         table_name: "table_1".to_string(),
    ///         columns: vec![],
    ///         rows: vec![],
    ///      },
    /// ]};
    ///
    /// assert_eq!(dataset.table_count(), 1);
    ///
    pub fn table_count(&self) -> usize {
        self.tables.len()
    }
}

#[async_convert::async_trait]
impl TryFrom<HttpResponse> for KustoResponseDataSetV2 {
    type Error = Error;

    async fn try_from(response: HttpResponse) -> Result<Self> {
        let (_status_code, _header_map, pinned_stream) = response.deconstruct();
        let data = pinned_stream.collect().await?;
        let tables: Vec<V2QueryResult> = serde_json::from_slice(&data)?;
        Ok(Self { results: tables })
    }
}

#[async_convert::async_trait]
impl TryFrom<HttpResponse> for KustoResponseDataSetV1 {
    type Error = Error;

    async fn try_from(response: HttpResponse) -> Result<Self> {
        let (_status_code, _header_map, pinned_stream) = response.deconstruct();
        let data = pinned_stream.collect().await?;
        Ok(serde_json::from_slice(&data)?)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::PathBuf;

    #[test]
    fn load_response_data() {
        let data = r#"{
            "Tables": [{
                "TableName": "Table_0",
                "Columns": [{
                    "ColumnName": "Text",
                    "DataType": "String"
                }],
                "Rows": [["Hello, World!"]]
            }]
        }"#;

        let parsed = serde_json::from_str::<KustoResponseDataSetV1>(data).expect("Failed to parse");

        assert_eq!(parsed.tables[0].columns[0].column_name, "Text");
        assert_eq!(parsed.tables[0].rows[0][0], "Hello, World!");
    }

    #[test]
    fn load_adminthenquery_response() {
        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        path.push("tests/inputs/adminthenquery.json");

        let data = std::fs::read_to_string(&path)
            .unwrap_or_else(|_| panic!("Failed to read {}", path.display()));

        let parsed = serde_json::from_str::<KustoResponseDataSetV1>(&data)
            .expect("Failed to parse response");
        assert_eq!(parsed.table_count(), 4);
    }
}


use arrow::datatypes::{DataType, Field, Schema, TimeUnit};
use std::sync::Arc;
mod setup;

// copied from datafusion repository.
// https://github.com/apache/arrow-datafusion/blob/41b4e491663029f653e491b110d0b5e74d08a0b6/datafusion/core/src/test_util.rs#L36
macro_rules! assert_batches_eq {
    ($EXPECTED_LINES: expr, $CHUNKS: expr) => {
        let expected_lines: Vec<String> = $EXPECTED_LINES.iter().map(|&s| s.into()).collect();

        let formatted = arrow::util::pretty::pretty_format_batches($CHUNKS)
            .unwrap()
            .to_string();

        let actual_lines: Vec<&str> = formatted.trim().lines().collect();

        assert_eq!(
            expected_lines, actual_lines,
            "\n\nexpected:\n\n{:#?}\nactual:\n\n{:#?}\n\n",
            expected_lines, actual_lines
        );
    };
}

#[tokio::test]
async fn arrow_roundtrip() {
    let (client, database) = setup::create_kusto_client();

    let query = "
        datatable(
            id:int,
            string_col:string,
            bool_col:bool,
            int_col:int,
            bigint_col:long,
            float_col:real,
            timestamp_col:datetime,
            duration_col:timespan
        ) [
            6, 'Hello', true, 0, 0, 0, datetime(2009-04-01 00:00:00), timespan(1.00:00:00.0000001),
            7, 'World', false, 1, 10, 1.1, datetime(2009-04-01 00:01:00), timespan(-00:01:00.0001001),
        ]
    ";
    let response = client
        .execute_query(database, query, None)
        .await
        .expect("Failed to run query");
    let batches = response
        .record_batches()
        .collect::<Result<Vec<_>, _>>()
        .expect("Failed to collect batches");

    let expected_schema = Arc::new(Schema::new(vec![
        Field::new("id", DataType::Int32, true),
        Field::new("string_col", DataType::Utf8, true),
        Field::new("bool_col", DataType::Boolean, true),
        Field::new("int_col", DataType::Int32, true),
        Field::new("bigint_col", DataType::Int64, true),
        Field::new("float_col", DataType::Float64, true),
        Field::new(
            "timestamp_col",
            DataType::Timestamp(TimeUnit::Nanosecond, None),
            true,
        ),
        Field::new(
            "duration_col",
            DataType::Duration(TimeUnit::Nanosecond),
            true,
        ),
    ]));
    let expected = vec![
        "+----+------------+----------+---------+------------+-----------+---------------------+",
        "| id | string_col | bool_col | int_col | bigint_col | float_col | timestamp_col       |",
        "+----+------------+----------+---------+------------+-----------+---------------------+",
        "| 6  | Hello      | true     | 0       | 0          | 0.0       | 2009-04-01T00:00:00 |",
        "| 7  | World      | false    | 1       | 10         | 1.1       | 2009-04-01T00:01:00 |",
        "+----+------------+----------+---------+------------+-----------+---------------------+",
    ];
    assert_batches_eq!(
        expected,
        // we have to de-select the duration column, since pretty printing is not supported in arrow
        &[batches[0]
            .project(&[0, 1, 2, 3, 4, 5, 6])
            .expect("Failed to project numbers")]
    );
    assert_eq!(expected_schema, batches[0].schema());
}


use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::str::FromStr;
use time::Duration;

use azure_kusto_data::types::{KustoDateTime, KustoDuration};
use decimal::d128;
use uuid::Uuid;

mod setup;

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
struct Item {
    vnum: i32,
    vdec: d128,
    vdate: KustoDateTime,
    vspan: KustoDuration,
    vobj: Value,
    vb: bool,
    vreal: f64,
    vstr: String,
    vlong: i64,
    vguid: Uuid,
}

#[tokio::test]
async fn create_query_delete_table() {
    let (client, database) = setup::create_kusto_client();

    let query = r#".set-or-replace KustoRsTest <| datatable(vnum:int, vdec:decimal, vdate:datetime, vspan:timespan, vobj:dynamic, vb:bool, vreal:real, vstr:string, vlong:long, vguid:guid)
[
    1, decimal(2.00000000000001), datetime(2020-03-04T14:05:01.3109965Z), time(01:23:45.6789000), dynamic({
  "moshe": "value"
}), true, 0.01, "asdf", 9223372036854775807, guid(74be27de-1e4e-49d9-b579-fe0b331d3642),
2, decimal(5.00000000000005), datetime(2022-05-06T16:07:03.1234300Z), time(04:56:59.9120000), dynamic({
"moshe": "value2"
}), false, 0.05, "qwerty", 9223372036854775806, guid(f6e97f76-8b73-45c0-b9ef-f68e8f897713),
3, decimal(9.9999999999999), datetime(2023-07-08T18:09:05.5678000Z), time(07:43:12.3456000), dynamic({
"moshe": "value3"
}), true, 0.99, "zxcv", 9223372036854775805, guid(d8e3575c-a7a0-47b3-8c73-9a7a6aaabc12),
]
"#;
    let response = client
        .execute_command(database.clone(), query, None)
        .await
        .expect("Failed to run query");

    assert_eq!(response.table_count(), 1);

    let query = ".show tables | where TableName == \"KustoRsTest\"";
    let response = client
        .execute_command(database.clone(), query, None)
        .await
        .expect("Failed to run query");

    assert_eq!(response.table_count(), 4);

    let query = "KustoRsTest";
    let response = client
        .execute_query(database.clone(), query, None)
        .await
        .expect("Failed to run query");

    let results = response.into_primary_results().next().expect("No results");

    let rows = results.rows;

    let expected = vec![
        Item {
            vnum: 1,
            vdec: d128!(2.00000000000001),
            vdate: KustoDateTime::from_str("2020-03-04T14:05:01.3109965Z").unwrap(),
            vspan: KustoDuration::from(
                Duration::seconds(3600 + 23 * 60 + 45) + Duration::microseconds(678900),
            ),
            vobj: Value::Object(serde_json::Map::from_iter(vec![(
                "moshe".to_string(),
                Value::String("value".to_string()),
            )])),
            vb: true,
            vreal: 0.01,
            vstr: "asdf".to_string(),
            vlong: 9223372036854775807,
            vguid: Uuid::parse_str("74be27de-1e4e-49d9-b579-fe0b331d3642").unwrap(),
        },
        Item {
            vnum: 2,
            vdec: d128!(5.00000000000005),
            vdate: KustoDateTime::from_str("2022-05-06T16:07:03.1234300Z").unwrap(),
            vspan: KustoDuration::from(
                Duration::seconds(4 * 3600 + 56 * 60 + 59) + Duration::microseconds(912000),
            ),
            vobj: Value::Object(serde_json::Map::from_iter(vec![(
                "moshe".to_string(),
                Value::String("value2".to_string()),
            )])),
            vb: false,
            vreal: 0.05,
            vstr: "qwerty".to_string(),
            vlong: 9223372036854775806,
            vguid: Uuid::parse_str("f6e97f76-8b73-45c0-b9ef-f68e8f897713").unwrap(),
        },
        Item {
            vnum: 3,
            vdec: d128!(9.9999999999999),
            vdate: KustoDateTime::from_str("2023-07-08T18:09:05.5678000Z").unwrap(),
            vspan: KustoDuration::from(
                Duration::seconds(7 * 3600 + 43 * 60 + 12) + Duration::microseconds(345600),
            ),
            vobj: Value::Object(serde_json::Map::from_iter(vec![(
                "moshe".to_string(),
                Value::String("value3".to_string()),
            )])),
            vb: true,
            vreal: 0.99,
            vstr: "zxcv".to_string(),
            vlong: 9223372036854775805,
            vguid: Uuid::parse_str("d8e3575c-a7a0-47b3-8c73-9a7a6aaabc12").unwrap(),
        },
    ];

    let items =
        serde_json::from_value::<Vec<Item>>(Value::Array(rows)).expect("Failed to deserialize");

    assert_eq!(items, expected);

    let query = ".drop table KustoRsTest | where TableName == \"KustoRsTest\"";
    let response = client
        .execute_command(database.clone(), query, None)
        .await
        .expect("Failed to run query");

    assert_eq!(response.tables[0].rows.len(), 0);
}


use azure_kusto_data::prelude::*;
use dotenv::dotenv;

#[must_use]
pub fn create_kusto_client() -> (KustoClient, String) {
    dotenv().ok();
    (
        ConnectionString::with_default_auth(
            std::env::var("KUSTO_CLUSTER_URL").expect("Set env variable KUSTO_CLUSTER_URL first!"),
        )
        .try_into()
        .expect("Failed to create KustoClient"),
        std::env::var("KUSTO_DATABASE").expect("Set env variable KUSTO_DATABASE first!"),
    )
}