use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::path::{Path, PathBuf};

use crate::app::reports::inspect::{
    RuntimeCompactEventDispatchClusterConditionTuple, RuntimeCompactEventDispatchClusterOccurrence,
    RuntimeCompactEventDispatchClusterReport, RuntimeCompactEventDispatchClusterRow,
    RuntimeProfileBlockExportDocument,
};
use rrt_runtime::inspect::smp::bundle::{SmpInspectionReport, inspect_smp_file};

pub(crate) fn build_runtime_compact_event_dispatch_cluster_report(
    root_path: &Path,
) -> Result<RuntimeCompactEventDispatchClusterReport, Box<dyn std::error::Error>> {
    let mut input_paths = Vec::new();
    collect_compact_event_dispatch_cluster_input_paths(root_path, &mut input_paths)?;
    input_paths.sort();

    let mut maps_with_event_runtime_collection = 0usize;
    let mut maps_with_dispatch_strip_records = 0usize;
    let mut dispatch_strip_record_count = 0usize;
    let mut dispatch_strip_records_with_trigger_kind = 0usize;
    let mut dispatch_strip_records_missing_trigger_kind = 0usize;
    let mut dispatch_strip_payload_families = BTreeMap::<String, usize>::new();
    let mut dispatch_descriptor_occurrence_counts = BTreeMap::<String, usize>::new();
    let mut dispatch_descriptor_map_counts = BTreeMap::<String, usize>::new();
    let mut add_building_dispatch_record_count = 0usize;
    let mut add_building_dispatch_records_with_trigger_kind = 0usize;
    let mut add_building_dispatch_records_missing_trigger_kind = 0usize;
    let mut add_building_descriptor_occurrence_counts = BTreeMap::<String, usize>::new();
    let mut add_building_descriptor_map_counts = BTreeMap::<String, usize>::new();
    let mut add_building_row_shape_occurrence_counts = BTreeMap::<String, usize>::new();
    let mut add_building_row_shape_map_counts = BTreeMap::<String, usize>::new();
    let mut add_building_signature_family_occurrence_counts = BTreeMap::<String, usize>::new();
    let mut add_building_signature_family_map_counts = BTreeMap::<String, usize>::new();
    let mut add_building_condition_tuple_occurrence_counts = BTreeMap::<String, usize>::new();
    let mut add_building_condition_tuple_map_counts = BTreeMap::<String, usize>::new();
    let mut add_building_signature_condition_cluster_occurrence_counts =
        BTreeMap::<String, usize>::new();
    let mut add_building_signature_condition_cluster_map_counts = BTreeMap::<String, usize>::new();
    let mut signature_condition_cluster_descriptor_keys =
        BTreeMap::<String, BTreeSet<String>>::new();
    let mut add_building_signature_condition_clusters = BTreeSet::<String>::new();
    let mut dispatch_descriptor_occurrences =
        BTreeMap::<String, Vec<RuntimeCompactEventDispatchClusterOccurrence>>::new();
    let mut unknown_descriptor_occurrences =
        BTreeMap::<u32, Vec<RuntimeCompactEventDispatchClusterOccurrence>>::new();

    for path in &input_paths {
        let inspection = inspect_smp_file(path)?;
        let Some(summary) = inspection.event_runtime_collection_summary else {
            continue;
        };
        maps_with_event_runtime_collection += 1;

        let mut map_dispatch_strip_record_count = 0usize;
        let mut map_descriptor_keys = BTreeSet::<String>::new();
        let mut map_add_building_descriptor_keys = BTreeSet::<String>::new();
        let mut map_add_building_row_shapes = BTreeSet::<String>::new();
        let mut map_add_building_signature_families = BTreeSet::<String>::new();
        let mut map_add_building_condition_tuples = BTreeSet::<String>::new();
        let mut map_add_building_signature_condition_clusters = BTreeSet::<String>::new();
        for record in &summary.records {
            let matching_rows = record
                .grouped_effect_rows
                .iter()
                .filter(|row| compact_event_dispatch_strip_opcode(row.opcode))
                .fold(
                    BTreeMap::<u32, Vec<RuntimeCompactEventDispatchClusterRow>>::new(),
                    |mut grouped, row| {
                        grouped.entry(row.descriptor_id).or_default().push(
                            RuntimeCompactEventDispatchClusterRow {
                                group_index: row.group_index,
                                descriptor_id: row.descriptor_id,
                                descriptor_label: row.descriptor_label.clone(),
                                opcode: row.opcode,
                                raw_scalar_value: row.raw_scalar_value,
                            },
                        );
                        grouped
                    },
                );
            if matching_rows.is_empty() {
                continue;
            }

            map_dispatch_strip_record_count += 1;
            if record.trigger_kind.is_some() {
                dispatch_strip_records_with_trigger_kind += 1;
            } else {
                dispatch_strip_records_missing_trigger_kind += 1;
            }
            *dispatch_strip_payload_families
                .entry(record.payload_family.clone())
                .or_insert(0) += 1;
            let mut record_has_add_building = false;
            let condition_tuples = record
                .standalone_condition_rows
                .iter()
                .map(|row| RuntimeCompactEventDispatchClusterConditionTuple {
                    raw_condition_id: row.raw_condition_id,
                    subtype: row.subtype,
                    metric: row.metric.clone(),
                })
                .collect::<Vec<_>>();
            let signature_family = compact_event_signature_family_from_notes(&record.notes);
            let condition_tuple_family =
                compact_event_dispatch_condition_tuple_family(&condition_tuples);
            let row_shape_family = compact_event_dispatch_row_shape_family(&matching_rows);
            let signature_family_key = signature_family
                .clone()
                .unwrap_or_else(|| "unknown-signature-family".to_string());
            let signature_condition_cluster_key =
                compact_event_dispatch_signature_condition_cluster_key(
                    signature_family.as_deref(),
                    &condition_tuples,
                );

            for (descriptor_id, rows) in matching_rows {
                let occurrence = RuntimeCompactEventDispatchClusterOccurrence {
                    path: path.display().to_string(),
                    record_index: record.record_index,
                    live_entry_id: record.live_entry_id,
                    payload_family: record.payload_family.clone(),
                    trigger_kind: record.trigger_kind,
                    signature_family: signature_family.clone(),
                    condition_tuples: condition_tuples.clone(),
                    rows: rows.clone(),
                };
                let descriptor_key = compact_event_dispatch_descriptor_key(descriptor_id, &rows);
                signature_condition_cluster_descriptor_keys
                    .entry(signature_condition_cluster_key.clone())
                    .or_default()
                    .insert(descriptor_key.clone());
                *dispatch_descriptor_occurrence_counts
                    .entry(descriptor_key.clone())
                    .or_insert(0) += 1;
                map_descriptor_keys.insert(descriptor_key.clone());
                if compact_event_dispatch_add_building_descriptor_id(descriptor_id) {
                    record_has_add_building = true;
                    add_building_signature_condition_clusters
                        .insert(signature_condition_cluster_key.clone());
                    *add_building_descriptor_occurrence_counts
                        .entry(descriptor_key.clone())
                        .or_insert(0) += 1;
                    map_add_building_descriptor_keys.insert(descriptor_key.clone());
                    *add_building_row_shape_occurrence_counts
                        .entry(row_shape_family.clone())
                        .or_insert(0) += 1;
                    map_add_building_row_shapes.insert(row_shape_family.clone());
                    *add_building_signature_family_occurrence_counts
                        .entry(signature_family_key.clone())
                        .or_insert(0) += 1;
                    *add_building_condition_tuple_occurrence_counts
                        .entry(condition_tuple_family.clone())
                        .or_insert(0) += 1;
                    *add_building_signature_condition_cluster_occurrence_counts
                        .entry(signature_condition_cluster_key.clone())
                        .or_insert(0) += 1;
                    map_add_building_signature_families.insert(signature_family_key.clone());
                    map_add_building_condition_tuples.insert(condition_tuple_family.clone());
                    map_add_building_signature_condition_clusters
                        .insert(signature_condition_cluster_key.clone());
                }
                dispatch_descriptor_occurrences
                    .entry(descriptor_key)
                    .or_default()
                    .push(occurrence.clone());
                if rows.iter().all(|row| row.descriptor_label.is_none()) {
                    unknown_descriptor_occurrences
                        .entry(descriptor_id)
                        .or_default()
                        .push(occurrence);
                }
            }
            if record_has_add_building {
                add_building_dispatch_record_count += 1;
                if record.trigger_kind.is_some() {
                    add_building_dispatch_records_with_trigger_kind += 1;
                } else {
                    add_building_dispatch_records_missing_trigger_kind += 1;
                }
            }
        }

        if map_dispatch_strip_record_count > 0 {
            maps_with_dispatch_strip_records += 1;
            dispatch_strip_record_count += map_dispatch_strip_record_count;
        }
        for descriptor_key in map_descriptor_keys {
            *dispatch_descriptor_map_counts
                .entry(descriptor_key)
                .or_insert(0) += 1;
        }
        for descriptor_key in map_add_building_descriptor_keys {
            *add_building_descriptor_map_counts
                .entry(descriptor_key)
                .or_insert(0) += 1;
        }
        for row_shape in map_add_building_row_shapes {
            *add_building_row_shape_map_counts
                .entry(row_shape)
                .or_insert(0) += 1;
        }
        for signature_family in map_add_building_signature_families {
            *add_building_signature_family_map_counts
                .entry(signature_family)
                .or_insert(0) += 1;
        }
        for condition_tuple_family in map_add_building_condition_tuples {
            *add_building_condition_tuple_map_counts
                .entry(condition_tuple_family)
                .or_insert(0) += 1;
        }
        for signature_condition_cluster in map_add_building_signature_condition_clusters {
            *add_building_signature_condition_cluster_map_counts
                .entry(signature_condition_cluster)
                .or_insert(0) += 1;
        }
    }

    let unknown_descriptor_ids = unknown_descriptor_occurrences
        .keys()
        .copied()
        .collect::<Vec<_>>();
    let unknown_descriptor_special_condition_label_matches = unknown_descriptor_ids
        .iter()
        .filter_map(|descriptor_id| {
            special_condition_label_for_compact_dispatch_descriptor(*descriptor_id)
                .map(|label| format!("{descriptor_id} -> {label}"))
        })
        .collect::<Vec<_>>();
    let add_building_signature_condition_cluster_descriptor_keys =
        add_building_signature_condition_clusters
            .iter()
            .map(|cluster| {
                let keys = signature_condition_cluster_descriptor_keys
                    .get(cluster)
                    .map(|keys| keys.iter().cloned().collect::<Vec<_>>())
                    .unwrap_or_default();
                (cluster.clone(), keys)
            })
            .collect::<BTreeMap<_, _>>();
    let add_building_signature_condition_cluster_non_add_building_descriptor_keys =
        add_building_signature_condition_cluster_descriptor_keys
            .iter()
            .map(|(cluster, keys)| {
                let filtered = keys
                    .iter()
                    .filter(|key| !key.contains("Add Building"))
                    .cloned()
                    .collect::<Vec<_>>();
                (cluster.clone(), filtered)
            })
            .collect::<BTreeMap<_, _>>();

    Ok(RuntimeCompactEventDispatchClusterReport {
        maps_scanned: input_paths.len(),
        maps_with_event_runtime_collection,
        maps_with_dispatch_strip_records,
        dispatch_strip_record_count,
        dispatch_strip_records_with_trigger_kind,
        dispatch_strip_records_missing_trigger_kind,
        dispatch_strip_payload_families,
        dispatch_descriptor_occurrence_counts,
        dispatch_descriptor_map_counts,
        dispatch_descriptor_occurrences,
        unknown_descriptor_ids,
        unknown_descriptor_special_condition_label_matches,
        unknown_descriptor_occurrences,
        add_building_dispatch_record_count,
        add_building_dispatch_records_with_trigger_kind,
        add_building_dispatch_records_missing_trigger_kind,
        add_building_descriptor_occurrence_counts,
        add_building_descriptor_map_counts,
        add_building_row_shape_occurrence_counts,
        add_building_row_shape_map_counts,
        add_building_signature_family_occurrence_counts,
        add_building_signature_family_map_counts,
        add_building_condition_tuple_occurrence_counts,
        add_building_condition_tuple_map_counts,
        add_building_signature_condition_cluster_occurrence_counts,
        add_building_signature_condition_cluster_map_counts,
        add_building_signature_condition_cluster_descriptor_keys,
        add_building_signature_condition_cluster_non_add_building_descriptor_keys,
    })
}

pub(crate) fn build_profile_block_export_document(
    smp_path: &Path,
    inspection: &SmpInspectionReport,
) -> Result<RuntimeProfileBlockExportDocument, Box<dyn std::error::Error>> {
    if let Some(probe) = &inspection.classic_rehydrate_profile_probe {
        return Ok(RuntimeProfileBlockExportDocument {
            source_path: smp_path.display().to_string(),
            profile_kind: "classic-rehydrate-profile".to_string(),
            profile_family: probe.profile_family.clone(),
            payload: serde_json::to_value(probe)?,
        });
    }

    if let Some(probe) = &inspection.rt3_105_packed_profile_probe {
        return Ok(RuntimeProfileBlockExportDocument {
            source_path: smp_path.display().to_string(),
            profile_kind: "rt3-105-packed-profile".to_string(),
            profile_family: probe.profile_family.clone(),
            payload: serde_json::to_value(probe)?,
        });
    }

    Err(format!(
        "{} did not expose an exportable packed-profile block",
        smp_path.display()
    )
    .into())
}

pub(crate) fn compact_event_dispatch_add_building_descriptor_id(descriptor_id: u32) -> bool {
    (503..=613).contains(&descriptor_id)
}

pub(crate) fn compact_event_dispatch_strip_opcode(opcode: u8) -> bool {
    matches!(opcode, 0x04..=0x08 | 0x0d | 0x10..=0x13 | 0x16)
}

pub(crate) fn compact_event_signature_family_from_notes(notes: &[String]) -> Option<String> {
    notes.iter().find_map(|note| {
        note.strip_prefix("compact signature family = ")
            .map(ToString::to_string)
    })
}

pub(crate) fn special_condition_label_for_compact_dispatch_descriptor(
    descriptor_id: u32,
) -> Option<&'static str> {
    let band_index = descriptor_id.checked_sub(535)? as usize;
    crate::app::runtime_scan::common::SPECIAL_CONDITION_LABELS
        .get(band_index)
        .copied()
}

pub(crate) fn collect_compact_event_dispatch_cluster_input_paths(
    root_path: &Path,
    out: &mut Vec<PathBuf>,
) -> Result<(), Box<dyn std::error::Error>> {
    let metadata = match fs::symlink_metadata(root_path) {
        Ok(metadata) => metadata,
        Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => return Ok(()),
        Err(err) => return Err(err.into()),
    };
    if metadata.file_type().is_symlink() {
        return Ok(());
    }

    if root_path.is_file() {
        if root_path
            .extension()
            .and_then(|ext| ext.to_str())
            .is_some_and(|ext| ext.eq_ignore_ascii_case("gmp"))
        {
            out.push(root_path.to_path_buf());
        }
        return Ok(());
    }

    let entries = match fs::read_dir(root_path) {
        Ok(entries) => entries,
        Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => return Ok(()),
        Err(err) => return Err(err.into()),
    };

    for entry in entries {
        let entry = entry?;
        let path = entry.path();
        if path.is_dir() {
            collect_compact_event_dispatch_cluster_input_paths(&path, out)?;
            continue;
        }
        if path
            .extension()
            .and_then(|ext| ext.to_str())
            .is_some_and(|ext| ext.eq_ignore_ascii_case("gmp"))
        {
            out.push(path);
        }
    }

    Ok(())
}

pub(crate) fn compact_event_dispatch_descriptor_key(
    descriptor_id: u32,
    rows: &[RuntimeCompactEventDispatchClusterRow],
) -> String {
    rows.first()
        .and_then(|row| row.descriptor_label.as_deref())
        .map(|label| format!("{descriptor_id} {label}"))
        .unwrap_or_else(|| descriptor_id.to_string())
}

pub(crate) fn compact_event_dispatch_row_shape_family(
    grouped_rows: &BTreeMap<u32, Vec<RuntimeCompactEventDispatchClusterRow>>,
) -> String {
    let mut parts = grouped_rows
        .values()
        .flat_map(|rows| rows.iter())
        .map(|row| {
            format!(
                "{}:{}:{}",
                row.group_index, row.opcode, row.raw_scalar_value
            )
        })
        .collect::<Vec<_>>();
    if parts.is_empty() {
        return "[]".to_string();
    }
    parts.sort();
    format!("[{}]", parts.join(","))
}

pub(crate) fn compact_event_dispatch_condition_tuple_family(
    tuples: &[RuntimeCompactEventDispatchClusterConditionTuple],
) -> String {
    if tuples.is_empty() {
        return "[]".to_string();
    }
    let parts = tuples
        .iter()
        .map(|tuple| match &tuple.metric {
            Some(metric) => format!("{}:{}:{}", tuple.raw_condition_id, tuple.subtype, metric),
            None => format!("{}:{}", tuple.raw_condition_id, tuple.subtype),
        })
        .collect::<Vec<_>>();
    format!("[{}]", parts.join(","))
}

pub(crate) fn compact_event_dispatch_signature_condition_cluster_key(
    signature_family: Option<&str>,
    tuples: &[RuntimeCompactEventDispatchClusterConditionTuple],
) -> String {
    format!(
        "{} :: {}",
        signature_family.unwrap_or("unknown-signature-family"),
        compact_event_dispatch_condition_tuple_family(tuples)
    )
}
