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

use rrt_runtime::inspect::smp::{
    bundle::inspect_smp_file,
    profiles::{SmpClassicPackedProfileBlock, SmpRt3105PackedProfileBlock},
};
use serde::Serialize;

#[derive(Debug, Clone, Serialize)]
pub(crate) struct RuntimeClassicProfileSample {
    pub(crate) path: String,
    pub(crate) profile_family: String,
    pub(crate) progress_32dc_offset: usize,
    pub(crate) progress_3714_offset: usize,
    pub(crate) progress_3715_offset: usize,
    pub(crate) packed_profile_offset: usize,
    pub(crate) packed_profile_len: usize,
    pub(crate) packed_profile_block: SmpClassicPackedProfileBlock,
}

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

#[derive(Debug, Clone, Serialize)]
pub(crate) struct RuntimeRt3105ProfileSample {
    pub(crate) path: String,
    pub(crate) profile_family: String,
    pub(crate) packed_profile_offset: usize,
    pub(crate) packed_profile_len: usize,
    pub(crate) packed_profile_block: SmpRt3105PackedProfileBlock,
}

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

pub(crate) fn compare_classic_profile(
    smp_paths: &[PathBuf],
) -> Result<(), Box<dyn std::error::Error>> {
    let samples = smp_paths
        .iter()
        .map(|path| load_classic_profile_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_classic_profile_samples(&samples)?;
    let report = RuntimeClassicProfileComparisonReport {
        file_count: samples.len(),
        matches: differences.is_empty(),
        common_profile_family,
        difference_count: differences.len(),
        differences,
        samples,
    };
    println!("{}", serde_json::to_string_pretty(&report)?);
    Ok(())
}

pub(crate) fn compare_rt3_105_profile(
    smp_paths: &[PathBuf],
) -> Result<(), Box<dyn std::error::Error>> {
    let samples = smp_paths
        .iter()
        .map(|path| load_rt3_105_profile_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_rt3_105_profile_samples(&samples)?;
    let report = RuntimeRt3105ProfileComparisonReport {
        file_count: samples.len(),
        matches: differences.is_empty(),
        common_profile_family,
        difference_count: differences.len(),
        differences,
        samples,
    };
    println!("{}", serde_json::to_string_pretty(&report)?);
    Ok(())
}

pub(crate) fn load_classic_profile_sample(
    smp_path: &Path,
) -> Result<RuntimeClassicProfileSample, Box<dyn std::error::Error>> {
    let inspection = inspect_smp_file(smp_path)?;
    let probe = inspection.classic_rehydrate_profile_probe.ok_or_else(|| {
        format!(
            "{} did not expose a classic rehydrate packed-profile block",
            smp_path.display()
        )
    })?;

    Ok(RuntimeClassicProfileSample {
        path: smp_path.display().to_string(),
        profile_family: probe.profile_family,
        progress_32dc_offset: probe.progress_32dc_offset,
        progress_3714_offset: probe.progress_3714_offset,
        progress_3715_offset: probe.progress_3715_offset,
        packed_profile_offset: probe.packed_profile_offset,
        packed_profile_len: probe.packed_profile_len,
        packed_profile_block: probe.packed_profile_block,
    })
}

pub(crate) fn load_rt3_105_profile_sample(
    smp_path: &Path,
) -> Result<RuntimeRt3105ProfileSample, Box<dyn std::error::Error>> {
    let inspection = inspect_smp_file(smp_path)?;
    let probe = inspection.rt3_105_packed_profile_probe.ok_or_else(|| {
        format!(
            "{} did not expose an RT3 1.05 packed-profile block",
            smp_path.display()
        )
    })?;

    Ok(RuntimeRt3105ProfileSample {
        path: smp_path.display().to_string(),
        profile_family: probe.profile_family,
        packed_profile_offset: probe.packed_profile_offset,
        packed_profile_len: probe.packed_profile_len,
        packed_profile_block: probe.packed_profile_block,
    })
}

pub(crate) fn diff_classic_profile_samples(
    samples: &[RuntimeClassicProfileSample],
) -> 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,
                    "progress_32dc_offset": sample.progress_32dc_offset,
                    "progress_3714_offset": sample.progress_3714_offset,
                    "progress_3715_offset": sample.progress_3715_offset,
                    "packed_profile_offset": sample.packed_profile_offset,
                    "packed_profile_len": sample.packed_profile_len,
                    "packed_profile_block": sample.packed_profile_block,
                }),
            )
        })
        .collect::<Vec<_>>();
    let mut differences = Vec::new();
    collect_json_multi_differences("$", &labeled_values, &mut differences);
    Ok(differences)
}

pub(crate) fn diff_rt3_105_profile_samples(
    samples: &[RuntimeRt3105ProfileSample],
) -> 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,
                    "packed_profile_offset": sample.packed_profile_offset,
                    "packed_profile_len": sample.packed_profile_len,
                    "packed_profile_block": sample.packed_profile_block,
                }),
            )
        })
        .collect::<Vec<_>>();
    let mut differences = Vec::new();
    collect_json_multi_differences("$", &labeled_values, &mut differences);
    Ok(differences)
}
