use super::common::{RuntimeClassicProfileDifference, collect_json_multi_differences};
use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};

use rrt_runtime::inspect::smp::bundle::inspect_smp_file;
use serde::Serialize;

#[derive(Debug, Clone, Serialize)]
pub(crate) struct RuntimeRecipeBookLineSample {
    pub(crate) path: String,
    pub(crate) profile_family: String,
    pub(crate) source_kind: String,
    pub(crate) book_count: usize,
    pub(crate) book_stride_hex: String,
    pub(crate) line_count: usize,
    pub(crate) line_stride_hex: String,
    pub(crate) book_head_kind_by_index: BTreeMap<String, String>,
    pub(crate) book_line_area_kind_by_index: BTreeMap<String, String>,
    pub(crate) max_annual_production_word_hex_by_book: BTreeMap<String, String>,
    pub(crate) line_kind_by_path: BTreeMap<String, String>,
    pub(crate) mode_word_hex_by_path: BTreeMap<String, String>,
    pub(crate) annual_amount_word_hex_by_path: BTreeMap<String, String>,
    pub(crate) supplied_cargo_token_word_hex_by_path: BTreeMap<String, String>,
    pub(crate) demanded_cargo_token_word_hex_by_path: BTreeMap<String, String>,
}

#[derive(Debug, Serialize)]
pub(crate) struct RuntimeRecipeBookLineComparisonReport {
    pub(crate) file_count: usize,
    pub(crate) matches: bool,
    pub(crate) content_matches: bool,
    pub(crate) common_profile_family: Option<String>,
    pub(crate) samples: Vec<RuntimeRecipeBookLineSample>,
    pub(crate) difference_count: usize,
    pub(crate) differences: Vec<RuntimeClassicProfileDifference>,
    pub(crate) content_difference_count: usize,
    pub(crate) content_differences: Vec<RuntimeClassicProfileDifference>,
}

#[derive(Debug, Serialize)]
pub(crate) struct RuntimeRecipeBookLineFieldSummary {
    pub(crate) line_path: String,
    pub(crate) file_count_present: usize,
    pub(crate) distinct_value_count: usize,
    pub(crate) sample_value_hexes: Vec<String>,
}

pub(crate) fn compare_recipe_book_lines(
    smp_paths: &[PathBuf],
) -> Result<(), Box<dyn std::error::Error>> {
    let samples = smp_paths
        .iter()
        .map(|path| load_recipe_book_line_sample(path))
        .collect::<Result<Vec<_>, _>>()?;
    let common_profile_family = samples
        .first()
        .map(|sample| sample.profile_family.clone())
        .filter(|family| {
            samples
                .iter()
                .all(|sample| sample.profile_family == *family)
        });
    let differences = diff_recipe_book_line_samples(&samples)?;
    let content_differences = diff_recipe_book_line_content_samples(&samples)?;
    let report = RuntimeRecipeBookLineComparisonReport {
        file_count: samples.len(),
        matches: differences.is_empty(),
        content_matches: content_differences.is_empty(),
        common_profile_family,
        difference_count: differences.len(),
        differences,
        content_difference_count: content_differences.len(),
        content_differences,
        samples,
    };
    println!("{}", serde_json::to_string_pretty(&report)?);
    Ok(())
}

pub(crate) fn load_recipe_book_line_sample(
    smp_path: &Path,
) -> Result<RuntimeRecipeBookLineSample, Box<dyn std::error::Error>> {
    let inspection = inspect_smp_file(smp_path)?;
    let probe = inspection.recipe_book_summary_probe.ok_or_else(|| {
        format!(
            "{} did not expose a grounded recipe-book summary block",
            smp_path.display()
        )
    })?;

    let mut book_head_kind_by_index = BTreeMap::new();
    let mut book_line_area_kind_by_index = BTreeMap::new();
    let mut max_annual_production_word_hex_by_book = BTreeMap::new();
    let mut line_kind_by_path = BTreeMap::new();
    let mut mode_word_hex_by_path = BTreeMap::new();
    let mut annual_amount_word_hex_by_path = BTreeMap::new();
    let mut supplied_cargo_token_word_hex_by_path = BTreeMap::new();
    let mut demanded_cargo_token_word_hex_by_path = BTreeMap::new();

    for book in &probe.books {
        let book_key = format!("book{:02}", book.book_index);
        book_head_kind_by_index.insert(book_key.clone(), book.head_kind.clone());
        book_line_area_kind_by_index.insert(book_key.clone(), book.line_area_kind.clone());
        max_annual_production_word_hex_by_book.insert(
            book_key.clone(),
            book.max_annual_production_word_hex.clone(),
        );
        for line in &book.lines {
            let line_key = format!("{book_key}.line{:02}", line.line_index);
            line_kind_by_path.insert(line_key.clone(), line.line_kind.clone());
            mode_word_hex_by_path.insert(line_key.clone(), line.mode_word_hex.clone());
            annual_amount_word_hex_by_path
                .insert(line_key.clone(), line.annual_amount_word_hex.clone());
            supplied_cargo_token_word_hex_by_path
                .insert(line_key.clone(), line.supplied_cargo_token_word_hex.clone());
            demanded_cargo_token_word_hex_by_path
                .insert(line_key.clone(), line.demanded_cargo_token_word_hex.clone());
        }
    }

    Ok(RuntimeRecipeBookLineSample {
        path: smp_path.display().to_string(),
        profile_family: probe.profile_family,
        source_kind: probe.source_kind,
        book_count: probe.book_count,
        book_stride_hex: probe.book_stride_hex,
        line_count: probe.line_count,
        line_stride_hex: probe.line_stride_hex,
        book_head_kind_by_index,
        book_line_area_kind_by_index,
        max_annual_production_word_hex_by_book,
        line_kind_by_path,
        mode_word_hex_by_path,
        annual_amount_word_hex_by_path,
        supplied_cargo_token_word_hex_by_path,
        demanded_cargo_token_word_hex_by_path,
    })
}

pub(crate) fn diff_recipe_book_line_samples(
    samples: &[RuntimeRecipeBookLineSample],
) -> Result<Vec<RuntimeClassicProfileDifference>, Box<dyn std::error::Error>> {
    let labeled_values = samples
        .iter()
        .map(|sample| {
            (
                sample.path.clone(),
                serde_json::json!({
                    "profile_family": sample.profile_family,
                    "source_kind": sample.source_kind,
                    "book_count": sample.book_count,
                    "book_stride_hex": sample.book_stride_hex,
                    "line_count": sample.line_count,
                    "line_stride_hex": sample.line_stride_hex,
                    "book_head_kind_by_index": sample.book_head_kind_by_index,
                    "book_line_area_kind_by_index": sample.book_line_area_kind_by_index,
                    "max_annual_production_word_hex_by_book": sample.max_annual_production_word_hex_by_book,
                    "line_kind_by_path": sample.line_kind_by_path,
                    "mode_word_hex_by_path": sample.mode_word_hex_by_path,
                    "annual_amount_word_hex_by_path": sample.annual_amount_word_hex_by_path,
                    "supplied_cargo_token_word_hex_by_path": sample.supplied_cargo_token_word_hex_by_path,
                    "demanded_cargo_token_word_hex_by_path": sample.demanded_cargo_token_word_hex_by_path,
                }),
            )
        })
        .collect::<Vec<_>>();
    let mut differences = Vec::new();
    collect_json_multi_differences("$", &labeled_values, &mut differences);
    Ok(differences)
}

pub(crate) fn diff_recipe_book_line_content_samples(
    samples: &[RuntimeRecipeBookLineSample],
) -> Result<Vec<RuntimeClassicProfileDifference>, Box<dyn std::error::Error>> {
    let labeled_values = samples
        .iter()
        .map(|sample| {
            (
                sample.path.clone(),
                serde_json::json!({
                    "book_count": sample.book_count,
                    "book_stride_hex": sample.book_stride_hex,
                    "line_count": sample.line_count,
                    "line_stride_hex": sample.line_stride_hex,
                    "book_head_kind_by_index": sample.book_head_kind_by_index,
                    "book_line_area_kind_by_index": sample.book_line_area_kind_by_index,
                    "max_annual_production_word_hex_by_book": sample.max_annual_production_word_hex_by_book,
                    "line_kind_by_path": sample.line_kind_by_path,
                    "mode_word_hex_by_path": sample.mode_word_hex_by_path,
                    "annual_amount_word_hex_by_path": sample.annual_amount_word_hex_by_path,
                    "supplied_cargo_token_word_hex_by_path": sample.supplied_cargo_token_word_hex_by_path,
                    "demanded_cargo_token_word_hex_by_path": sample.demanded_cargo_token_word_hex_by_path,
                }),
            )
        })
        .collect::<Vec<_>>();
    let mut differences = Vec::new();
    collect_json_multi_differences("$", &labeled_values, &mut differences);
    Ok(differences)
}

pub(crate) fn intersect_nonzero_recipe_line_paths<'a>(
    maps: impl Iterator<Item = &'a BTreeMap<String, String>>,
) -> Vec<String> {
    let mut maps = maps.peekable();
    if maps.peek().is_none() {
        return Vec::new();
    }

    let mut stable = maps
        .next()
        .map(|map| map.keys().cloned().collect::<BTreeSet<_>>())
        .unwrap_or_default();
    for map in maps {
        let current = map.keys().cloned().collect::<BTreeSet<_>>();
        stable = stable.intersection(&current).cloned().collect();
    }
    stable.into_iter().collect()
}

pub(crate) fn build_recipe_line_field_summaries<'a>(
    maps: impl Iterator<Item = &'a BTreeMap<String, String>>,
) -> Vec<RuntimeRecipeBookLineFieldSummary> {
    let mut value_sets = BTreeMap::<String, BTreeSet<String>>::new();
    let mut counts = BTreeMap::<String, usize>::new();
    for map in maps {
        for (line_path, value_hex) in map {
            *counts.entry(line_path.clone()).or_default() += 1;
            value_sets
                .entry(line_path.clone())
                .or_default()
                .insert(value_hex.clone());
        }
    }

    counts
        .into_iter()
        .map(
            |(line_path, file_count_present)| RuntimeRecipeBookLineFieldSummary {
                line_path: line_path.clone(),
                file_count_present,
                distinct_value_count: value_sets.get(&line_path).map(BTreeSet::len).unwrap_or(0),
                sample_value_hexes: value_sets
                    .get(&line_path)
                    .map(|values| values.iter().take(8).cloned().collect())
                    .unwrap_or_default(),
            },
        )
        .collect()
}
