//! Output model types for the MIG-driven mapping pipeline.
//!
//! Public types `Interchange`, `Nachricht`, `DynamicInterchange`, `DynamicNachricht`,
//! `Interchangedaten`, `Nachrichtendaten` are re-exported from `bo4e-edifact-types`.
//!
//! Internal engine types `MappedMessage` and `MappedTransaktion` carry forward-mapping
//! results including `nesting_info` metadata that is not part of the public API.

use mig_types::segment::OwnedSegment;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

// Re-export public model types from bo4e-edifact-types
pub use bo4e_edifact_types::{
    DynamicInterchange, DynamicNachricht, Interchange, Interchangedaten, Nachricht,
    Nachrichtendaten,
};

/// Internal engine type for a forward-mapped transaction.
///
/// Contains all BO4E entities (including prozessdaten) in `stammdaten`,
/// plus nesting distribution info used by the reverse mapper.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MappedTransaktion {
    /// All BO4E entities mapped from this transaction's segment groups.
    /// Keys are entity names in camelCase (e.g., "prozessdaten", "marktlokation", "messlokation").
    pub stammdaten: serde_json::Value,

    /// Nesting distribution info for transaction-level entities.
    ///
    /// Maps entity key (camelCase) -> parent rep index for each child element.
    /// Used by the reverse mapper to distribute children among parent group reps
    /// within a transaction (e.g., SG36->SG40 in PRICAT).
    /// Derived from the tree structure during forward mapping; never serialized.
    #[serde(skip)]
    pub nesting_info: HashMap<String, Vec<usize>>,
}

/// Intermediate result from mapping a single message's assembled tree.
///
/// Contains message-level stammdaten and per-transaction results.
/// Used by `MappingEngine::map_interchange()` before wrapping into `Nachricht`.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MappedMessage {
    /// Message-level BO4E entities (e.g., Marktteilnehmer from SG2).
    pub stammdaten: serde_json::Value,

    /// Per-transaction results (one per SG4 instance).
    pub transaktionen: Vec<MappedTransaktion>,

    /// Nesting distribution info for message-level entities.
    ///
    /// Maps entity key (camelCase) -> parent rep index for each child element.
    /// Used by the reverse mapper to distribute children among parent group reps.
    /// Derived from the tree structure during forward mapping; never serialized.
    #[serde(skip)]
    pub nesting_info: HashMap<String, Vec<usize>>,
}

impl MappedMessage {
    /// Convert this internal engine result into a public `DynamicNachricht`.
    ///
    /// Each `MappedTransaktion.stammdaten` becomes a transaction entry in the
    /// `DynamicNachricht.transaktionen` Vec.
    pub fn into_dynamic_nachricht(self, nachrichtendaten: Nachrichtendaten) -> DynamicNachricht {
        Nachricht {
            nachrichtendaten,
            stammdaten: self.stammdaten,
            transaktionen: self
                .transaktionen
                .into_iter()
                .map(|t| t.stammdaten)
                .collect(),
        }
    }
}

/// Extract message reference and message type from a UNH segment.
pub fn extract_unh_fields(unh: &OwnedSegment) -> (String, String) {
    let referenz = unh.get_element(0).to_string();
    let typ = unh.get_component(1, 0).to_string();
    (referenz, typ)
}

/// Extract typed interchange-level metadata from envelope segments (UNB).
pub fn extract_interchangedaten(envelope: &[OwnedSegment]) -> Interchangedaten {
    let mut result = Interchangedaten::default();

    for seg in envelope {
        if seg.is("UNB") {
            let val = |s: &str| {
                if s.is_empty() {
                    None
                } else {
                    Some(s.to_string())
                }
            };
            result.syntax_kennung = val(seg.get_component(0, 0));
            result.absender_code = val(seg.get_component(1, 0));
            result.empfaenger_code = val(seg.get_component(2, 0));
            result.datum = val(seg.get_component(3, 0));
            result.zeit = val(seg.get_component(3, 1));
            result.interchange_ref = val(seg.get_element(4));
        }
    }

    result
}

/// Extract interchange-level metadata from envelope segments (UNB) as JSON.
///
/// Kept for backward compatibility. Prefer `extract_interchangedaten()` for typed access.
pub fn extract_nachrichtendaten(envelope: &[OwnedSegment]) -> serde_json::Value {
    let data = extract_interchangedaten(envelope);
    serde_json::to_value(&data).unwrap_or_default()
}

/// Rebuild a UNB (interchange header) segment from typed `Interchangedaten`.
///
/// This is the inverse of `extract_interchangedaten()`.
/// Fields not present get sensible defaults (UNOC:3, "500" qualifier).
pub fn rebuild_unb_from_interchangedaten(data: &Interchangedaten) -> OwnedSegment {
    let syntax = data.syntax_kennung.as_deref().unwrap_or("UNOC");
    let sender = data.absender_code.as_deref().unwrap_or("");
    let receiver = data.empfaenger_code.as_deref().unwrap_or("");
    let datum = data.datum.as_deref().unwrap_or("");
    let zeit = data.zeit.as_deref().unwrap_or("");
    let interchange_ref = data.interchange_ref.as_deref().unwrap_or("00000");

    OwnedSegment {
        id: "UNB".to_string(),
        elements: vec![
            vec![syntax.to_string(), "3".to_string()],
            vec![sender.to_string(), "500".to_string()],
            vec![receiver.to_string(), "500".to_string()],
            vec![datum.to_string(), zeit.to_string()],
            vec![interchange_ref.to_string()],
        ],
        segment_number: 0,
    }
}

/// Rebuild a UNB (interchange header) segment from nachrichtendaten JSON.
///
/// This is the inverse of `extract_nachrichtendaten()`.
/// Fields not present in the JSON get sensible defaults (UNOC:3, "500" qualifier).
pub fn rebuild_unb(nachrichtendaten: &serde_json::Value) -> OwnedSegment {
    let syntax = nachrichtendaten
        .get("syntaxKennung")
        .and_then(|v| v.as_str())
        .unwrap_or("UNOC");
    let sender = nachrichtendaten
        .get("absenderCode")
        .and_then(|v| v.as_str())
        .unwrap_or("");
    let receiver = nachrichtendaten
        .get("empfaengerCode")
        .and_then(|v| v.as_str())
        .unwrap_or("");
    let datum = nachrichtendaten
        .get("datum")
        .and_then(|v| v.as_str())
        .unwrap_or("");
    let zeit = nachrichtendaten
        .get("zeit")
        .and_then(|v| v.as_str())
        .unwrap_or("");
    let interchange_ref = nachrichtendaten
        .get("interchangeRef")
        .and_then(|v| v.as_str())
        .unwrap_or("00000");

    OwnedSegment {
        id: "UNB".to_string(),
        elements: vec![
            vec![syntax.to_string(), "3".to_string()],
            vec![sender.to_string(), "500".to_string()],
            vec![receiver.to_string(), "500".to_string()],
            vec![datum.to_string(), zeit.to_string()],
            vec![interchange_ref.to_string()],
        ],
        segment_number: 0,
    }
}

/// Rebuild a UNH (message header) segment from reference number and message type.
///
/// Produces: `UNH+referenz+typ:D:11A:UN:S2.1`
pub fn rebuild_unh(referenz: &str, nachrichten_typ: &str) -> OwnedSegment {
    OwnedSegment {
        id: "UNH".to_string(),
        elements: vec![
            vec![referenz.to_string()],
            vec![
                nachrichten_typ.to_string(),
                "D".to_string(),
                "11A".to_string(),
                "UN".to_string(),
                "S2.1".to_string(),
            ],
        ],
        segment_number: 0,
    }
}

/// Rebuild a UNT (message trailer) segment.
///
/// Produces: `UNT+count+referenz`
/// `segment_count` includes UNH and UNT themselves.
pub fn rebuild_unt(segment_count: usize, referenz: &str) -> OwnedSegment {
    OwnedSegment {
        id: "UNT".to_string(),
        elements: vec![vec![segment_count.to_string()], vec![referenz.to_string()]],
        segment_number: 0,
    }
}

/// Rebuild a UNZ (interchange trailer) segment.
///
/// Produces: `UNZ+count+ref`
pub fn rebuild_unz(message_count: usize, interchange_ref: &str) -> OwnedSegment {
    OwnedSegment {
        id: "UNZ".to_string(),
        elements: vec![
            vec![message_count.to_string()],
            vec![interchange_ref.to_string()],
        ],
        segment_number: 0,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_mapped_transaktion_serde_roundtrip() {
        let tx = MappedTransaktion {
            stammdaten: serde_json::json!({
                "prozessdaten": {
                    "vorgangId": "TX001",
                    "transaktionsgrund": "E01"
                },
                "marktlokation": { "marktlokationsId": "DE000111222333" }
            }),
            nesting_info: Default::default(),
        };

        let json = serde_json::to_string(&tx).unwrap();
        let de: MappedTransaktion = serde_json::from_str(&json).unwrap();
        assert_eq!(
            de.stammdaten["prozessdaten"]["vorgangId"].as_str().unwrap(),
            "TX001"
        );
        assert!(de.stammdaten["marktlokation"].is_object());
    }

    #[test]
    fn test_dynamic_nachricht_serde_roundtrip() {
        let msg: DynamicNachricht = Nachricht {
            nachrichtendaten: Nachrichtendaten {
                unh_referenz: "00001".to_string(),
                nachrichten_typ: "UTILMD".to_string(),
            },
            stammdaten: serde_json::json!({
                "marktteilnehmer": [
                    { "marktrolle": "MS", "rollencodenummer": "9900123" }
                ]
            }),
            transaktionen: vec![serde_json::json!({})],
        };

        let json = serde_json::to_string(&msg).unwrap();
        let de: DynamicNachricht = serde_json::from_str(&json).unwrap();
        assert_eq!(de.nachrichtendaten.unh_referenz, "00001");
        assert_eq!(de.nachrichtendaten.nachrichten_typ, "UTILMD");
        assert_eq!(de.transaktionen.len(), 1);
    }

    #[test]
    fn test_dynamic_interchange_serde_roundtrip() {
        let interchange: DynamicInterchange = Interchange {
            interchangedaten: Interchangedaten {
                absender_code: Some("9900123456789".to_string()),
                empfaenger_code: Some("9900987654321".to_string()),
                ..Default::default()
            },
            nachrichten: vec![Nachricht {
                nachrichtendaten: Nachrichtendaten {
                    unh_referenz: "00001".to_string(),
                    nachrichten_typ: "UTILMD".to_string(),
                },
                stammdaten: serde_json::json!({}),
                transaktionen: vec![],
            }],
        };

        let json = serde_json::to_string_pretty(&interchange).unwrap();
        let de: DynamicInterchange = serde_json::from_str(&json).unwrap();
        assert_eq!(de.nachrichten.len(), 1);
        assert_eq!(de.nachrichten[0].nachrichtendaten.unh_referenz, "00001");
    }

    #[test]
    fn test_extract_interchangedaten_from_segments() {
        let envelope = vec![OwnedSegment {
            id: "UNB".to_string(),
            elements: vec![
                vec!["UNOC".to_string(), "3".to_string()],
                vec!["9900123456789".to_string(), "500".to_string()],
                vec!["9900987654321".to_string(), "500".to_string()],
                vec!["210101".to_string(), "1200".to_string()],
                vec!["REF001".to_string()],
            ],
            segment_number: 0,
        }];

        let data = extract_interchangedaten(&envelope);
        assert_eq!(data.absender_code.as_deref(), Some("9900123456789"));
        assert_eq!(data.empfaenger_code.as_deref(), Some("9900987654321"));
        assert_eq!(data.interchange_ref.as_deref(), Some("REF001"));
        assert_eq!(data.syntax_kennung.as_deref(), Some("UNOC"));
        assert_eq!(data.datum.as_deref(), Some("210101"));
        assert_eq!(data.zeit.as_deref(), Some("1200"));
    }

    #[test]
    fn test_extract_envelope_from_segments_json() {
        let envelope = vec![OwnedSegment {
            id: "UNB".to_string(),
            elements: vec![
                vec!["UNOC".to_string(), "3".to_string()],
                vec!["9900123456789".to_string(), "500".to_string()],
                vec!["9900987654321".to_string(), "500".to_string()],
                vec!["210101".to_string(), "1200".to_string()],
                vec!["REF001".to_string()],
            ],
            segment_number: 0,
        }];

        let nd = extract_nachrichtendaten(&envelope);
        assert_eq!(nd["absenderCode"].as_str().unwrap(), "9900123456789");
        assert_eq!(nd["empfaengerCode"].as_str().unwrap(), "9900987654321");
        assert_eq!(nd["interchangeRef"].as_str().unwrap(), "REF001");
        assert_eq!(nd["syntaxKennung"].as_str().unwrap(), "UNOC");
        assert_eq!(nd["datum"].as_str().unwrap(), "210101");
        assert_eq!(nd["zeit"].as_str().unwrap(), "1200");
    }

    #[test]
    fn test_extract_unh_fields() {
        let unh = OwnedSegment {
            id: "UNH".to_string(),
            elements: vec![
                vec!["MSG001".to_string()],
                vec![
                    "UTILMD".to_string(),
                    "D".to_string(),
                    "11A".to_string(),
                    "UN".to_string(),
                    "S2.1".to_string(),
                ],
            ],
            segment_number: 0,
        };

        let (referenz, typ) = extract_unh_fields(&unh);
        assert_eq!(referenz, "MSG001");
        assert_eq!(typ, "UTILMD");
    }

    #[test]
    fn test_rebuild_unb_from_interchangedaten_typed() {
        let data = Interchangedaten {
            syntax_kennung: Some("UNOC".to_string()),
            absender_code: Some("9900123456789".to_string()),
            empfaenger_code: Some("9900987654321".to_string()),
            datum: Some("210101".to_string()),
            zeit: Some("1200".to_string()),
            interchange_ref: Some("REF001".to_string()),
        };

        let unb = rebuild_unb_from_interchangedaten(&data);
        assert_eq!(unb.id, "UNB");
        assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
        assert_eq!(unb.elements[1][0], "9900123456789");
        assert_eq!(unb.elements[2][0], "9900987654321");
        assert_eq!(unb.elements[3], vec!["210101", "1200"]);
        assert_eq!(unb.elements[4], vec!["REF001"]);
    }

    #[test]
    fn test_rebuild_unb_from_nachrichtendaten() {
        let nd = serde_json::json!({
            "syntaxKennung": "UNOC",
            "absenderCode": "9900123456789",
            "empfaengerCode": "9900987654321",
            "datum": "210101",
            "zeit": "1200",
            "interchangeRef": "REF001"
        });

        let unb = rebuild_unb(&nd);
        assert_eq!(unb.id, "UNB");
        assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
        assert_eq!(unb.elements[1][0], "9900123456789");
        assert_eq!(unb.elements[2][0], "9900987654321");
        assert_eq!(unb.elements[3], vec!["210101", "1200"]);
        assert_eq!(unb.elements[4], vec!["REF001"]);
    }

    #[test]
    fn test_rebuild_unb_defaults() {
        let nd = serde_json::json!({});
        let unb = rebuild_unb(&nd);
        assert_eq!(unb.id, "UNB");
        assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
    }

    #[test]
    fn test_rebuild_unh() {
        let unh = rebuild_unh("00001", "UTILMD");
        assert_eq!(unh.id, "UNH");
        assert_eq!(unh.elements[0], vec!["00001"]);
        assert_eq!(unh.elements[1][0], "UTILMD");
        assert_eq!(unh.elements[1][1], "D");
        assert_eq!(unh.elements[1][2], "11A");
        assert_eq!(unh.elements[1][3], "UN");
        assert_eq!(unh.elements[1][4], "S2.1");
    }

    #[test]
    fn test_rebuild_unt() {
        let unt = rebuild_unt(25, "00001");
        assert_eq!(unt.id, "UNT");
        assert_eq!(unt.elements[0], vec!["25"]);
        assert_eq!(unt.elements[1], vec!["00001"]);
    }

    #[test]
    fn test_rebuild_unz() {
        let unz = rebuild_unz(1, "REF001");
        assert_eq!(unz.id, "UNZ");
        assert_eq!(unz.elements[0], vec!["1"]);
        assert_eq!(unz.elements[1], vec!["REF001"]);
    }

    #[test]
    fn test_roundtrip_interchangedaten_rebuild() {
        let original = OwnedSegment {
            id: "UNB".to_string(),
            elements: vec![
                vec!["UNOC".to_string(), "3".to_string()],
                vec!["9900123456789".to_string(), "500".to_string()],
                vec!["9900987654321".to_string(), "500".to_string()],
                vec!["210101".to_string(), "1200".to_string()],
                vec!["REF001".to_string()],
            ],
            segment_number: 0,
        };

        let data = extract_interchangedaten(&[original]);
        let rebuilt = rebuild_unb_from_interchangedaten(&data);
        assert_eq!(rebuilt.elements[0], vec!["UNOC", "3"]);
        assert_eq!(rebuilt.elements[1][0], "9900123456789");
        assert_eq!(rebuilt.elements[2][0], "9900987654321");
        assert_eq!(rebuilt.elements[3], vec!["210101", "1200"]);
        assert_eq!(rebuilt.elements[4], vec!["REF001"]);
    }

    #[test]
    fn test_roundtrip_nachrichtendaten_rebuild() {
        let original = OwnedSegment {
            id: "UNB".to_string(),
            elements: vec![
                vec!["UNOC".to_string(), "3".to_string()],
                vec!["9900123456789".to_string(), "500".to_string()],
                vec!["9900987654321".to_string(), "500".to_string()],
                vec!["210101".to_string(), "1200".to_string()],
                vec!["REF001".to_string()],
            ],
            segment_number: 0,
        };

        let nd = extract_nachrichtendaten(&[original]);
        let rebuilt = rebuild_unb(&nd);
        assert_eq!(rebuilt.elements[0], vec!["UNOC", "3"]);
        assert_eq!(rebuilt.elements[1][0], "9900123456789");
        assert_eq!(rebuilt.elements[2][0], "9900987654321");
        assert_eq!(rebuilt.elements[3], vec!["210101", "1200"]);
        assert_eq!(rebuilt.elements[4], vec!["REF001"]);
    }

    #[test]
    fn test_into_dynamic_nachricht() {
        let mapped = MappedMessage {
            stammdaten: serde_json::json!({"marktteilnehmer": []}),
            transaktionen: vec![MappedTransaktion {
                stammdaten: serde_json::json!({"prozessdaten": {"id": "1"}}),
                nesting_info: Default::default(),
            }],
            nesting_info: Default::default(),
        };

        let nd = Nachrichtendaten {
            unh_referenz: "00001".to_string(),
            nachrichten_typ: "UTILMD".to_string(),
        };

        let nachricht = mapped.into_dynamic_nachricht(nd);
        assert_eq!(nachricht.nachrichtendaten.unh_referenz, "00001");
        assert_eq!(nachricht.transaktionen.len(), 1);
        assert!(nachricht.transaktionen[0]["prozessdaten"].is_object());
    }
}
