Documentation
use std::process::Command;

use diwe::config::Configuration;
use liwe::query::block::parse_block_predicate;
use liwe::query::{parse_filter_expression, parse_operation, OperationKind};
use liwe::schema::compile_schema;
use serde_yaml::Value;

const INDEX: &str = include_str!("../docs/index.txt");
const QUERY: &str = include_str!("../docs/query.md");
const CONFIG: &str = include_str!("../docs/config.md");
const SCHEMA: &str = include_str!("../docs/schema.md");

fn run_docs(args: &[&str]) -> std::process::Output {
    Command::new(crate::common::get_iwe_binary_path())
        .arg("docs")
        .args(args)
        .output()
        .expect("Failed to execute iwe docs")
}

fn fenced_blocks(source: &str, language: &str) -> Vec<String> {
    let mut blocks = Vec::new();
    let mut current: Option<String> = None;
    for line in source.lines() {
        match current.as_mut() {
            Some(block) => {
                if line.trim_end() == "```" {
                    blocks.push(current.take().unwrap());
                } else {
                    block.push_str(line);
                    block.push('\n');
                }
            }
            None => {
                let trimmed = line.trim_end();
                if trimmed == format!("```{}", language) || trimmed == format!("``` {}", language) {
                    current = Some(String::new());
                }
            }
        }
    }
    blocks
}

#[test]
fn test_docs_index() {
    let output = run_docs(&[]);
    assert!(output.status.success());
    assert_eq!(String::from_utf8_lossy(&output.stdout), INDEX);
}

#[test]
fn test_docs_query() {
    let output = run_docs(&["query"]);
    assert!(output.status.success());
    assert_eq!(String::from_utf8_lossy(&output.stdout), QUERY);
}

#[test]
fn test_docs_config() {
    let output = run_docs(&["config"]);
    assert!(output.status.success());
    assert_eq!(String::from_utf8_lossy(&output.stdout), CONFIG);
}

#[test]
fn test_docs_schema() {
    let output = run_docs(&["schema"]);
    assert!(output.status.success());
    assert_eq!(String::from_utf8_lossy(&output.stdout), SCHEMA);
}

#[test]
fn test_docs_rejects_unknown_topic() {
    let output = run_docs(&["unknown"]);
    assert!(!output.status.success());
}

#[test]
fn test_query_doc_examples_parse() {
    let examples = fenced_blocks(QUERY, "yaml");
    assert!(!examples.is_empty());
    for example in examples {
        let operation = [
            OperationKind::Find,
            OperationKind::Count,
            OperationKind::Update,
            OperationKind::Delete,
        ]
        .into_iter()
        .any(|kind| parse_operation(&example, kind).is_ok());
        let filter = parse_filter_expression(&example).is_ok();
        let predicate = serde_yaml::from_str::<Value>(&example)
            .map(|value| parse_block_predicate(&value, "docs").is_ok())
            .unwrap_or(false);
        assert!(
            operation || filter || predicate,
            "query example does not parse as an operation, filter, or block predicate:\n{}",
            example
        );
    }
}

#[test]
fn test_config_doc_examples_parse() {
    let examples = fenced_blocks(CONFIG, "toml");
    assert!(!examples.is_empty());
    for example in examples {
        if let Err(error) = toml::from_str::<Configuration>(&example) {
            panic!("config example does not parse:\n{}\n{}", example, error);
        }
    }
}

#[test]
fn test_schema_doc_examples_compile() {
    let examples = fenced_blocks(SCHEMA, "yaml");
    assert!(!examples.is_empty());
    for example in examples {
        if let Err(errors) = compile_schema(&example) {
            panic!(
                "schema example does not compile:\n{}\n{:?}",
                example, errors
            );
        }
    }
}

#[test]
fn test_schema_doc_config_examples_parse() {
    let examples = fenced_blocks(SCHEMA, "toml");
    assert!(!examples.is_empty());
    for example in examples {
        if let Err(error) = toml::from_str::<Configuration>(&example) {
            panic!(
                "schema config example does not parse:\n{}\n{}",
                example, error
            );
        }
    }
}