use super::common::{
    POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET, SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT,
    SPECIAL_CONDITION_COUNT, SPECIAL_CONDITIONS_OFFSET, aligned_runtime_rule_known_label,
    aligned_runtime_rule_lane_kind, collect_special_conditions_input_paths,
};
use crate::app::runtime_compare::{classify_candidate_table_header_profile, read_u32_le};
use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::path::Path;

use serde::Serialize;

#[derive(Debug, Clone)]
pub(crate) struct RuntimeAlignedRuntimeRuleBandScanSample {
    pub(crate) path: String,
    pub(crate) profile_family: String,
    pub(crate) source_kind: String,
    pub(crate) nonzero_band_indices: Vec<usize>,
    pub(crate) values_by_band_index: BTreeMap<usize, String>,
}

#[derive(Debug, Serialize)]
pub(crate) struct RuntimeAlignedRuntimeRuleBandOffsetSummary {
    pub(crate) band_index: usize,
    pub(crate) relative_offset_hex: String,
    pub(crate) lane_kind: String,
    pub(crate) known_label: Option<String>,
    pub(crate) file_count_present: usize,
    pub(crate) distinct_value_count: usize,
    pub(crate) sample_value_hexes: Vec<String>,
}

#[derive(Debug, Serialize)]
pub(crate) struct RuntimeAlignedRuntimeRuleBandFamilySummary {
    pub(crate) profile_family: String,
    pub(crate) source_kinds: Vec<String>,
    pub(crate) file_count: usize,
    pub(crate) files_with_any_nonzero_count: usize,
    pub(crate) distinct_nonzero_index_set_count: usize,
    pub(crate) stable_nonzero_band_indices: Vec<usize>,
    pub(crate) union_nonzero_band_indices: Vec<usize>,
    pub(crate) offset_summaries: Vec<RuntimeAlignedRuntimeRuleBandOffsetSummary>,
    pub(crate) sample_paths: Vec<String>,
}

#[derive(Debug, Serialize)]
pub(crate) struct RuntimeAlignedRuntimeRuleBandScanReport {
    pub(crate) root_path: String,
    pub(crate) file_count: usize,
    pub(crate) files_with_probe_count: usize,
    pub(crate) files_with_any_nonzero_count: usize,
    pub(crate) skipped_file_count: usize,
    pub(crate) family_summaries: Vec<RuntimeAlignedRuntimeRuleBandFamilySummary>,
}

pub(crate) fn scan_aligned_runtime_rule_band(
    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_aligned_runtime_rule_band_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_count = samples
        .iter()
        .filter(|sample| !sample.nonzero_band_indices.is_empty())
        .count();

    let mut grouped = BTreeMap::<String, Vec<RuntimeAlignedRuntimeRuleBandScanSample>>::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)| {
            let file_count = samples.len();
            let files_with_any_nonzero_count = samples
                .iter()
                .filter(|sample| !sample.nonzero_band_indices.is_empty())
                .count();
            let source_kinds = samples
                .iter()
                .map(|sample| sample.source_kind.clone())
                .collect::<BTreeSet<_>>()
                .into_iter()
                .collect::<Vec<_>>();
            let distinct_nonzero_index_set_count = samples
                .iter()
                .map(|sample| sample.nonzero_band_indices.clone())
                .collect::<BTreeSet<_>>()
                .len();

            let stable_band_indices = if samples.is_empty() {
                BTreeSet::new()
            } else {
                let mut stable = samples[0]
                    .nonzero_band_indices
                    .iter()
                    .copied()
                    .collect::<BTreeSet<_>>();
                for sample in samples.iter().skip(1) {
                    let current = sample
                        .nonzero_band_indices
                        .iter()
                        .copied()
                        .collect::<BTreeSet<_>>();
                    stable = stable.intersection(&current).copied().collect();
                }
                stable
            };

            let mut band_values = BTreeMap::<usize, BTreeSet<String>>::new();
            let mut band_counts = BTreeMap::<usize, usize>::new();
            for sample in &samples {
                for band_index in &sample.nonzero_band_indices {
                    *band_counts.entry(*band_index).or_default() += 1;
                }
                for (band_index, value_hex) in &sample.values_by_band_index {
                    band_values
                        .entry(*band_index)
                        .or_default()
                        .insert(value_hex.clone());
                }
            }

            let offset_summaries = band_counts
                .into_iter()
                .map(
                    |(band_index, count)| RuntimeAlignedRuntimeRuleBandOffsetSummary {
                        band_index,
                        relative_offset_hex: format!("0x{:x}", band_index * 4),
                        lane_kind: aligned_runtime_rule_lane_kind(band_index).to_string(),
                        known_label: aligned_runtime_rule_known_label(band_index)
                            .map(str::to_string),
                        file_count_present: count,
                        distinct_value_count: band_values
                            .get(&band_index)
                            .map(BTreeSet::len)
                            .unwrap_or(0),
                        sample_value_hexes: band_values
                            .get(&band_index)
                            .map(|values| values.iter().take(8).cloned().collect())
                            .unwrap_or_default(),
                    },
                )
                .collect::<Vec<_>>();

            RuntimeAlignedRuntimeRuleBandFamilySummary {
                profile_family,
                source_kinds,
                file_count,
                files_with_any_nonzero_count,
                distinct_nonzero_index_set_count,
                stable_nonzero_band_indices: stable_band_indices.into_iter().collect(),
                union_nonzero_band_indices: band_values.keys().copied().collect(),
                offset_summaries,
                sample_paths: samples
                    .iter()
                    .take(12)
                    .map(|sample| sample.path.clone())
                    .collect(),
            }
        })
        .collect::<Vec<_>>();

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

pub(crate) fn load_aligned_runtime_rule_band_scan_sample(
    smp_path: &Path,
) -> Result<RuntimeAlignedRuntimeRuleBandScanSample, Box<dyn std::error::Error>> {
    let bytes = fs::read(smp_path)?;
    let table_len = SPECIAL_CONDITION_COUNT * 4;
    let table_end = SPECIAL_CONDITIONS_OFFSET
        .checked_add(table_len)
        .ok_or("special-conditions table overflow")?;
    if bytes.len() < POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET || bytes.len() < table_end {
        return Err(format!(
            "{} is too small for the fixed aligned-runtime-rule band",
            smp_path.display()
        )
        .into());
    }

    let extension = smp_path
        .extension()
        .and_then(|ext| ext.to_str())
        .map(|ext| ext.to_ascii_lowercase())
        .unwrap_or_default();
    let profile_family = classify_candidate_table_header_profile(Some(extension.clone()), &bytes);
    let source_kind = match extension.as_str() {
        "gmp" => "map-aligned-runtime-rule-band",
        "gms" => "save-aligned-runtime-rule-band",
        "gmx" => "sandbox-aligned-runtime-rule-band",
        _ => "aligned-runtime-rule-band",
    }
    .to_string();

    let mut nonzero_band_indices = Vec::new();
    let mut values_by_band_index = BTreeMap::new();
    for band_index in 0..SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT {
        let offset = SPECIAL_CONDITIONS_OFFSET + band_index * 4;
        let value = read_u32_le(&bytes, offset).ok_or_else(|| {
            format!(
                "{} is truncated inside the aligned-runtime-rule band",
                smp_path.display()
            )
        })?;
        if value == 0 {
            continue;
        }
        nonzero_band_indices.push(band_index);
        values_by_band_index.insert(band_index, format!("0x{value:08x}"));
    }

    Ok(RuntimeAlignedRuntimeRuleBandScanSample {
        path: smp_path.display().to_string(),
        profile_family,
        source_kind,
        nonzero_band_indices,
        values_by_band_index,
    })
}
