use super::common::collect_special_conditions_input_paths;
use crate::app::runtime_compare::{
    RuntimeRecipeBookLineFieldSummary, build_recipe_line_field_summaries,
    intersect_nonzero_recipe_line_paths, load_recipe_book_line_sample,
};
use std::collections::{BTreeMap, BTreeSet};
use std::path::Path;

use serde::Serialize;

#[derive(Debug, Clone)]
pub(crate) struct RuntimeRecipeBookLineScanSample {
    pub(crate) path: String,
    pub(crate) profile_family: String,
    pub(crate) source_kind: String,
    pub(crate) nonzero_mode_paths: BTreeMap<String, String>,
    pub(crate) nonzero_supplied_token_paths: BTreeMap<String, String>,
    pub(crate) nonzero_demanded_token_paths: BTreeMap<String, String>,
}

#[derive(Debug, Serialize)]
pub(crate) struct RuntimeRecipeBookLineFamilySummary {
    pub(crate) profile_family: String,
    pub(crate) source_kinds: Vec<String>,
    pub(crate) file_count: usize,
    pub(crate) files_with_any_nonzero_modes_count: usize,
    pub(crate) files_with_any_nonzero_supplied_tokens_count: usize,
    pub(crate) files_with_any_nonzero_demanded_tokens_count: usize,
    pub(crate) stable_nonzero_mode_paths: Vec<String>,
    pub(crate) stable_nonzero_supplied_token_paths: Vec<String>,
    pub(crate) stable_nonzero_demanded_token_paths: Vec<String>,
    pub(crate) mode_summaries: Vec<RuntimeRecipeBookLineFieldSummary>,
    pub(crate) supplied_token_summaries: Vec<RuntimeRecipeBookLineFieldSummary>,
    pub(crate) demanded_token_summaries: Vec<RuntimeRecipeBookLineFieldSummary>,
    pub(crate) sample_paths: Vec<String>,
}

#[derive(Debug, Serialize)]
pub(crate) struct RuntimeRecipeBookLineScanReport {
    pub(crate) root_path: String,
    pub(crate) file_count: usize,
    pub(crate) files_with_probe_count: usize,
    pub(crate) files_with_any_nonzero_modes_count: usize,
    pub(crate) files_with_any_nonzero_supplied_tokens_count: usize,
    pub(crate) files_with_any_nonzero_demanded_tokens_count: usize,
    pub(crate) skipped_file_count: usize,
    pub(crate) family_summaries: Vec<RuntimeRecipeBookLineFamilySummary>,
}

pub(crate) fn scan_recipe_book_lines(root_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
    let mut candidate_paths = Vec::new();
    collect_special_conditions_input_paths(root_path, &mut candidate_paths)?;

    let file_count = candidate_paths.len();
    let mut samples = Vec::new();
    let mut skipped_file_count = 0usize;
    for path in candidate_paths {
        match load_recipe_book_line_scan_sample(&path) {
            Ok(sample) => samples.push(sample),
            Err(_) => skipped_file_count += 1,
        }
    }

    let files_with_probe_count = samples.len();
    let files_with_any_nonzero_modes_count = samples
        .iter()
        .filter(|sample| !sample.nonzero_mode_paths.is_empty())
        .count();
    let files_with_any_nonzero_supplied_tokens_count = samples
        .iter()
        .filter(|sample| !sample.nonzero_supplied_token_paths.is_empty())
        .count();
    let files_with_any_nonzero_demanded_tokens_count = samples
        .iter()
        .filter(|sample| !sample.nonzero_demanded_token_paths.is_empty())
        .count();

    let mut grouped = BTreeMap::<String, Vec<RuntimeRecipeBookLineScanSample>>::new();
    for sample in samples {
        grouped
            .entry(sample.profile_family.clone())
            .or_default()
            .push(sample);
    }

    let family_summaries = grouped
        .into_iter()
        .map(
            |(profile_family, samples)| RuntimeRecipeBookLineFamilySummary {
                profile_family,
                source_kinds: samples
                    .iter()
                    .map(|sample| sample.source_kind.clone())
                    .collect::<BTreeSet<_>>()
                    .into_iter()
                    .collect(),
                file_count: samples.len(),
                files_with_any_nonzero_modes_count: samples
                    .iter()
                    .filter(|sample| !sample.nonzero_mode_paths.is_empty())
                    .count(),
                files_with_any_nonzero_supplied_tokens_count: samples
                    .iter()
                    .filter(|sample| !sample.nonzero_supplied_token_paths.is_empty())
                    .count(),
                files_with_any_nonzero_demanded_tokens_count: samples
                    .iter()
                    .filter(|sample| !sample.nonzero_demanded_token_paths.is_empty())
                    .count(),
                stable_nonzero_mode_paths: intersect_nonzero_recipe_line_paths(
                    samples.iter().map(|sample| &sample.nonzero_mode_paths),
                ),
                stable_nonzero_supplied_token_paths: intersect_nonzero_recipe_line_paths(
                    samples
                        .iter()
                        .map(|sample| &sample.nonzero_supplied_token_paths),
                ),
                stable_nonzero_demanded_token_paths: intersect_nonzero_recipe_line_paths(
                    samples
                        .iter()
                        .map(|sample| &sample.nonzero_demanded_token_paths),
                ),
                mode_summaries: build_recipe_line_field_summaries(
                    samples.iter().map(|sample| &sample.nonzero_mode_paths),
                ),
                supplied_token_summaries: build_recipe_line_field_summaries(
                    samples
                        .iter()
                        .map(|sample| &sample.nonzero_supplied_token_paths),
                ),
                demanded_token_summaries: build_recipe_line_field_summaries(
                    samples
                        .iter()
                        .map(|sample| &sample.nonzero_demanded_token_paths),
                ),
                sample_paths: samples
                    .iter()
                    .take(12)
                    .map(|sample| sample.path.clone())
                    .collect(),
            },
        )
        .collect::<Vec<_>>();

    let report = RuntimeRecipeBookLineScanReport {
        root_path: root_path.display().to_string(),
        file_count,
        files_with_probe_count,
        files_with_any_nonzero_modes_count,
        files_with_any_nonzero_supplied_tokens_count,
        files_with_any_nonzero_demanded_tokens_count,
        skipped_file_count,
        family_summaries,
    };
    println!("{}", serde_json::to_string_pretty(&report)?);
    Ok(())
}

pub(crate) fn load_recipe_book_line_scan_sample(
    smp_path: &Path,
) -> Result<RuntimeRecipeBookLineScanSample, Box<dyn std::error::Error>> {
    let sample = load_recipe_book_line_sample(smp_path)?;
    Ok(RuntimeRecipeBookLineScanSample {
        path: sample.path,
        profile_family: sample.profile_family,
        source_kind: sample.source_kind,
        nonzero_mode_paths: sample
            .mode_word_hex_by_path
            .into_iter()
            .filter(|(_, value)| value != "0x00000000")
            .collect(),
        nonzero_supplied_token_paths: sample
            .supplied_cargo_token_word_hex_by_path
            .into_iter()
            .filter(|(_, value)| value != "0x00000000")
            .collect(),
        nonzero_demanded_token_paths: sample
            .demanded_cargo_token_word_hex_by_path
            .into_iter()
            .filter(|(_, value)| value != "0x00000000")
            .collect(),
    })
}
