use std::collections::HashMap;
use std::sync::Arc;

use crate::catalog::{Catalog, CatalogProvider, Column, HamelinType};
use crate::tree::ast::dataset_identifier::QualifiedDatasetIdentifier;
use crate::tree::ast::identifier::SimpleIdentifier;
use crate::tree::ast::query::Query;
use crate::tree::options::{TemplateParameterKind, TypeCheckOptions};
use crate::tree::typed_ast::query::TypedStatement;
use crate::types::INTERVAL;
use crate::{parse, type_check_with_options};

#[test]
fn templated_from_intersects_row_schemas() {
    let mut catalog = Catalog::default();
    catalog.0.insert(
        QualifiedDatasetIdentifier::from_canonical_str("db:t_alice").unwrap(),
        vec![
            Column::new("id", HamelinType::Int),
            Column::new("x", HamelinType::String),
        ],
    );
    catalog.0.insert(
        QualifiedDatasetIdentifier::from_canonical_str("db:t_bob").unwrap(),
        vec![Column::new("id", HamelinType::Int)],
    );
    let provider = Arc::new(CatalogProvider::try_from(catalog).unwrap());
    let mut tmpl = HashMap::new();
    tmpl.insert(
        "which".to_string(),
        TemplateParameterKind::IdentifierFragment(vec!["t_alice".to_string(), "t_bob".to_string()]),
    );
    let q = parse(r#"FROM db:${which}"#).into_result().expect("parse");
    let typed: TypedStatement = type_check_with_options::<Query>(
        Arc::new(q),
        TypeCheckOptions::builder()
            .provider(provider)
            .maybe_template_parameters(Some(Arc::new(tmpl)))
            .build(),
    )
    .into_result()
    .expect("typecheck");
    let schema = typed.schema();
    assert!(schema.lookup(&SimpleIdentifier::new("id")).is_some());
    assert!(schema.lookup(&SimpleIdentifier::new("x")).is_none());
}

#[test]
fn templated_join_requires_explicit_alias() {
    let mut catalog = Catalog::default();
    catalog.0.insert(
        QualifiedDatasetIdentifier::from_canonical_str("db:base").unwrap(),
        vec![Column::new("id", HamelinType::Int)],
    );
    catalog.0.insert(
        QualifiedDatasetIdentifier::from_canonical_str("db:t_alice").unwrap(),
        vec![Column::new("id", HamelinType::Int)],
    );
    let provider = Arc::new(CatalogProvider::try_from(catalog).unwrap());
    let mut tmpl = HashMap::new();
    tmpl.insert(
        "which".to_string(),
        TemplateParameterKind::IdentifierFragment(vec!["t_alice".to_string()]),
    );
    let q = parse(r#"FROM db:base | JOIN db:${which} ON true"#)
        .into_result()
        .expect("parse");
    let err = type_check_with_options::<Query>(
        Arc::new(q),
        TypeCheckOptions::builder()
            .provider(provider)
            .maybe_template_parameters(Some(Arc::new(tmpl)))
            .build(),
    )
    .into_result()
    .expect_err("templated JOIN without alias should fail");
    assert!(err.to_string().contains("require an explicit alias"));
}

#[test]
fn templated_match_requires_explicit_alias() {
    let mut catalog = Catalog::default();
    catalog.0.insert(
        QualifiedDatasetIdentifier::from_canonical_str("db:t_alice").unwrap(),
        vec![Column::new("id", HamelinType::Int)],
    );
    let provider = Arc::new(CatalogProvider::try_from(catalog).unwrap());
    let mut tmpl = HashMap::new();
    tmpl.insert(
        "which".to_string(),
        TemplateParameterKind::IdentifierFragment(vec!["t_alice".to_string()]),
    );
    let q = parse(r#"MATCH db:${which}"#).into_result().expect("parse");
    let err = type_check_with_options::<Query>(
        Arc::new(q),
        TypeCheckOptions::builder()
            .provider(provider)
            .maybe_template_parameters(Some(Arc::new(tmpl)))
            .build(),
    )
    .into_result()
    .expect_err("templated MATCH without alias should fail");
    assert!(err.to_string().contains("require an explicit alias"));
}

#[test]
fn templated_ts_trunc_typechecks_with_interval_primitive() {
    let mut catalog = Catalog::default();
    catalog.0.insert(
        QualifiedDatasetIdentifier::from_canonical_str("db:events").unwrap(),
        vec![Column::new("timestamp", HamelinType::Timestamp)],
    );
    let provider = Arc::new(CatalogProvider::try_from(catalog).unwrap());
    let mut tmpl = HashMap::new();
    tmpl.insert(
        "timeslice".to_string(),
        TemplateParameterKind::Primitive(INTERVAL),
    );
    let q = parse("FROM db:events | SELECT bucket = timestamp @${timeslice}")
        .into_result()
        .expect("parse");
    type_check_with_options::<Query>(
        Arc::new(q),
        TypeCheckOptions::builder()
            .provider(provider)
            .maybe_template_parameters(Some(Arc::new(tmpl)))
            .build(),
    )
    .into_result()
    .expect("typecheck");
}

#[test]
fn interval_primitive_works_as_leaf_expression() {
    let mut catalog = Catalog::default();
    catalog.0.insert(
        QualifiedDatasetIdentifier::from_canonical_str("db:events").unwrap(),
        vec![Column::new("timestamp", HamelinType::Timestamp)],
    );
    let provider = Arc::new(CatalogProvider::try_from(catalog).unwrap());
    let mut tmpl = HashMap::new();
    tmpl.insert(
        "timeslice".to_string(),
        TemplateParameterKind::Primitive(INTERVAL),
    );
    let q = parse("FROM db:events | WHERE timestamp > now() - ${timeslice}")
        .into_result()
        .expect("parse");
    type_check_with_options::<Query>(
        Arc::new(q),
        TypeCheckOptions::builder()
            .provider(provider)
            .maybe_template_parameters(Some(Arc::new(tmpl)))
            .build(),
    )
    .into_result()
    .expect("typecheck");
}
