//! Bulk roundtrip tests for all 95 UTILMD Gas PIDs (FV2510).
//!
//! Uses generated fixtures from `example_market_communication_bo4e_transactions/UTILMD/FV2510/generated_gas/`.
//! Full pipeline: EDIFACT → tokenize → split → assemble → map_interchange
//! → map_interchange_reverse → disassemble → render → byte-identical comparison.
//!
//! Gas MIG/AHB XMLs are byte-identical between FV2504 and FV2510, so all PIDs
//! that pass in FV2504 should pass identically in FV2510.

mod common;

use common::{bo4e_validation, test_utils, utilmd_gas_fv2510};
use edifact_primitives::EdifactDelimiters;
use mig_assembly::assembler::Assembler;
use mig_assembly::disassembler::Disassembler;
use mig_assembly::renderer::render_edifact;
use mig_assembly::tokenize::{parse_to_segments, split_messages};
use mig_bo4e::engine::MappingEngine;
use test_case::test_case;

/// PIDs with known structural limitations preventing byte-identical roundtrip.
const KNOWN_INCOMPLETE: &[&str] = &[];

#[test_case("44001")]
#[test_case("44002")]
#[test_case("44003")]
#[test_case("44004")]
#[test_case("44005")]
#[test_case("44006")]
#[test_case("44007")]
#[test_case("44008")]
#[test_case("44009")]
#[test_case("44010")]
#[test_case("44011")]
#[test_case("44012")]
#[test_case("44013")]
#[test_case("44014")]
#[test_case("44015")]
#[test_case("44016")]
#[test_case("44017")]
#[test_case("44018")]
#[test_case("44019")]
#[test_case("44020")]
#[test_case("44021")]
#[test_case("44022")]
#[test_case("44023")]
#[test_case("44024")]
#[test_case("44035")]
#[test_case("44036")]
#[test_case("44037")]
#[test_case("44038")]
#[test_case("44039")]
#[test_case("44040")]
#[test_case("44041")]
#[test_case("44042")]
#[test_case("44043")]
#[test_case("44044")]
#[test_case("44051")]
#[test_case("44052")]
#[test_case("44053")]
#[test_case("44060")]
#[test_case("44096")]
#[test_case("44097")]
#[test_case("44101")]
#[test_case("44102")]
#[test_case("44103")]
#[test_case("44104")]
#[test_case("44105")]
#[test_case("44109")]
#[test_case("44110")]
#[test_case("44111")]
#[test_case("44112")]
#[test_case("44113")]
#[test_case("44115")]
#[test_case("44116")]
#[test_case("44117")]
#[test_case("44119")]
#[test_case("44120")]
#[test_case("44121")]
#[test_case("44123")]
#[test_case("44124")]
#[test_case("44129")]
#[test_case("44130")]
#[test_case("44132")]
#[test_case("44137")]
#[test_case("44138")]
#[test_case("44139")]
#[test_case("44140")]
#[test_case("44142")]
#[test_case("44143")]
#[test_case("44145")]
#[test_case("44146")]
#[test_case("44147")]
#[test_case("44148")]
#[test_case("44149")]
#[test_case("44150")]
#[test_case("44151")]
#[test_case("44152")]
#[test_case("44156")]
#[test_case("44157")]
#[test_case("44159")]
#[test_case("44160")]
#[test_case("44161")]
#[test_case("44162")]
#[test_case("44163")]
#[test_case("44164")]
#[test_case("44165")]
#[test_case("44166")]
#[test_case("44167")]
#[test_case("44168")]
#[test_case("44169")]
#[test_case("44170")]
#[test_case("44172")]
#[test_case("44175")]
#[test_case("44176")]
#[test_case("44180")]
#[test_case("44181")]
#[test_case("44182")]
fn test_pid_roundtrip(pid: &str) {
    if KNOWN_INCOMPLETE.contains(&pid) {
        eprintln!("PID {pid}: KNOWN_INCOMPLETE -- skipping");
        return;
    }
    run_generated_roundtrip(pid);
}

// ── SG2/SG3 normalization ──

/// Normalize SG2/SG3 block ordering in rendered EDIFACT.
fn normalize_sg2_sg3_ordering(rendered: &str) -> String {
    let segs: Vec<&str> = rendered.split('\'').collect();
    let mut result: Vec<&str> = Vec::new();
    let mut sg3_segs: Vec<&str> = Vec::new();
    let mut first_nad_idx: Option<usize> = None;
    let mut past_sg2 = false;

    for seg in &segs {
        let tag = seg.split('+').next().unwrap_or("");
        if tag == "NAD" && !past_sg2 {
            if first_nad_idx.is_none() {
                first_nad_idx = Some(result.len());
            }
            result.push(seg);
        } else if (tag == "CTA" || tag == "COM") && !past_sg2 {
            sg3_segs.push(seg);
        } else {
            if !past_sg2 {
                if let Some(idx) = first_nad_idx {
                    if !sg3_segs.is_empty() {
                        let insert_pos = idx + 1;
                        for (i, sg3) in sg3_segs.drain(..).enumerate() {
                            result.insert(insert_pos + i, sg3);
                        }
                    }
                    past_sg2 = true;
                }
            }
            result.push(seg);
        }
    }
    if !sg3_segs.is_empty() {
        if let Some(idx) = first_nad_idx {
            let insert_pos = idx + 1;
            for (i, sg3) in sg3_segs.into_iter().enumerate() {
                result.insert(insert_pos + i, sg3);
            }
        }
    }
    result.join("'")
}

// ── Roundtrip helper ──

fn run_generated_roundtrip(pid: &str) {
    let Some(fixture_path) = utilmd_gas_fv2510::discover_generated_fixture(pid) else {
        panic!("PID {pid}: no FV2510 generated fixture found");
    };

    let Some(filtered_mig) = utilmd_gas_fv2510::load_pid_filtered_mig(pid) else {
        panic!("PID {pid}: FV2510 MIG/AHB XML not available");
    };

    let tx_dir = utilmd_gas_fv2510::pid_dir(pid);
    if !utilmd_gas_fv2510::message_dir().exists() || !tx_dir.exists() {
        panic!("PID {pid}: FV2510 mapping directories not found");
    }
    let (msg_engine, tx_engine) = utilmd_gas_fv2510::load_split_engines(pid);

    let fixture_name = fixture_path.file_name().unwrap().to_str().unwrap();
    let edifact_input = std::fs::read_to_string(&fixture_path).unwrap();

    // Step 1: Tokenize and split
    let segments = parse_to_segments(edifact_input.as_bytes()).unwrap();
    let chunks = split_messages(segments).unwrap();
    assert!(
        !chunks.messages.is_empty(),
        "PID {pid} ({fixture_name}): should have at least one message"
    );

    let msg_chunk = &chunks.messages[0];
    let mut msg_segs = vec![msg_chunk.unh.clone()];
    msg_segs.extend(msg_chunk.body.iter().cloned());
    msg_segs.push(msg_chunk.unt.clone());

    // Step 2: Assemble with PID-filtered MIG
    let assembler = Assembler::new(&filtered_mig);
    let original_tree = assembler.assemble_generic(&msg_segs).unwrap();

    // Step 3: Forward mapping
    let mapped = MappingEngine::map_interchange(
        &msg_engine,
        &tx_engine,
        &original_tree,
        utilmd_gas_fv2510::TX_GROUP,
        true,
    );

    assert!(
        !mapped.transaktionen.is_empty(),
        "PID {pid} ({fixture_name}): forward mapping should produce at least one transaction"
    );

    // Step 3b: BO4E schema validation
    let mapped_for_validation = MappingEngine::map_interchange(
        &msg_engine,
        &tx_engine,
        &original_tree,
        utilmd_gas_fv2510::TX_GROUP,
        false,
    );
    bo4e_validation::validate_mapped_message(
        pid,
        fixture_name,
        &msg_engine,
        &tx_engine,
        &mapped_for_validation,
    );

    // Step 4: Reverse mapping
    let mut reverse_tree = MappingEngine::map_interchange_reverse(
        &msg_engine,
        &tx_engine,
        &mapped,
        utilmd_gas_fv2510::TX_GROUP,
        Some(&filtered_mig),
    );

    let unh_assembled = test_utils::owned_to_assembled(&msg_chunk.unh);
    reverse_tree.segments.insert(0, unh_assembled);
    reverse_tree.post_group_start += 1;

    let original_has_unt = original_tree.segments.last().map(|s| s.tag.as_str()) == Some("UNT");
    if original_has_unt {
        let unt_assembled = test_utils::owned_to_assembled(&msg_chunk.unt);
        reverse_tree.segments.push(unt_assembled);
    }

    // Step 5: Disassemble and render
    let disassembler = Disassembler::new(&filtered_mig);
    let delimiters = EdifactDelimiters::default();

    let original_dis = disassembler.disassemble(&original_tree);
    let original_rendered = render_edifact(&original_dis, &delimiters);

    let reverse_dis = disassembler.disassemble(&reverse_tree);
    let reverse_rendered = render_edifact(&reverse_dis, &delimiters);

    // Step 6: Normalize SG2/SG3 ordering
    let original_normalized = normalize_sg2_sg3_ordering(&original_rendered);
    let reverse_normalized = normalize_sg2_sg3_ordering(&reverse_rendered);

    // Step 7: Compare segment tags
    let orig_norm_segs: Vec<&str> = original_normalized.split('\'').collect();
    let rev_norm_segs: Vec<&str> = reverse_normalized.split('\'').collect();
    let original_tags: Vec<&str> = orig_norm_segs
        .iter()
        .map(|s| s.split('+').next().unwrap_or(""))
        .collect();
    let reverse_tags: Vec<&str> = rev_norm_segs
        .iter()
        .map(|s| s.split('+').next().unwrap_or(""))
        .collect();

    if original_tags != reverse_tags {
        eprintln!("PID {pid} ({fixture_name}): segment tag mismatch!");
        for (i, tag) in original_tags.iter().enumerate() {
            if reverse_tags.get(i) != Some(tag) {
                eprintln!(
                    "  first difference at position {i}: original={tag}, reversed={}",
                    reverse_tags.get(i).unwrap_or(&"<missing>")
                );
                break;
            }
        }
    }

    assert_eq!(
        original_tags, reverse_tags,
        "PID {pid} ({fixture_name}): segment tags should match after roundtrip"
    );

    // Step 8: Compare full rendered EDIFACT (normalized)
    assert_eq!(
        original_normalized, reverse_normalized,
        "PID {pid} ({fixture_name}): full EDIFACT roundtrip should be byte-identical"
    );

    eprintln!(
        "PID {pid}: {fixture_name} -- roundtrip OK, {} segments byte-identical",
        original_tags.len()
    );
}
