Imports System.Globalization
Imports System.IO
Imports io.konik.zugferd
Imports javax.management.relation
Imports Newtonsoft.Json
Imports VERAG_PROG_ALLGEMEIN
Imports VERAG_PROG_ALLGEMEIN.CC015B_TR_EXT_V4_3

Public Class cDakosyEZA_Greenpulse

    ' ==========================================================
    ' BuildByMrn_DY: Erzeugt Kafka-Objekt aus cDakosyEZA (DY)
    ' ==========================================================
    Public Shared Function BuildByMrn(eza As cDakosyEZA) As cATEZ_Greenpulse_KafkaDecs
        If eza Is Nothing Then Throw New ArgumentNullException(NameOf(eza))

        ' --- Adressen (Heuristik: CN=Importer, CZ=Exporter, DT=Declarant) ---
        Dim importer = GuessAddressByType(eza.eza_ADRESSEN, cVERAG_CustomsDeclarations.IMPORTER_ROLES)
        Dim exporter = GuessAddressByType(eza.eza_ADRESSEN, cVERAG_CustomsDeclarations.EXPORTER_ROLES)
        Dim declarant = GuessAddressByType(eza.eza_ADRESSEN, cVERAG_CustomsDeclarations.DECLARANT_ROLES)

        ' --- Rechnungsdaten (Unterlagen N380) ---
        Dim inv = If(eza.eza_UNTERLAGEN IsNot Nothing,
                 eza.eza_UNTERLAGEN.FirstOrDefault(Function(u) SafeStr(u.ezaUl_Art).Equals("N380", StringComparison.OrdinalIgnoreCase) _
                                                        AndAlso Not String.IsNullOrWhiteSpace(SafeStr(u.ezaUl_Nummer))),
                 Nothing)

        ' --- Kopf/Zielschema ---
        Dim dest As New cATEZ_Greenpulse_KafkaDecs With {
      .Declaration = New cATEZ_Greenpulse_KafkaDecs.DeclarationNode With {
        .DeclarationSourceId = SafeStr(eza.eza_dyaAnmID),
        .DeclarationNo = SafeStr(eza.eza_ANR),
        .DeclarationDate = ToDateYMD(FirstNonEmpty(eza.eza_Anmeldedatum, eza.eza_Erstellung)),
        .RequestedProcedure = SafeStr(eza.eza_VerfahrenBeantragtCode),
        .PreviousProcedure = "",
        .Goods = New List(Of cATEZ_Greenpulse_KafkaDecs.GoodItem)()
      },
      .Parties = New cATEZ_Greenpulse_KafkaDecs.PartiesNode With {
        .ImporterIdentificationNumber = FirstNonEmptyStr(If(importer IsNot Nothing, importer.ezaAd_TeilnehmerEORI, Nothing),
                                                         If(importer IsNot Nothing, importer.ezaAd_AdressCode, Nothing)),
        .ExporterIdentificationNumber = FirstNonEmptyStr(If(exporter IsNot Nothing, exporter.ezaAd_TeilnehmerEORI, Nothing),
                                                         If(exporter IsNot Nothing, exporter.ezaAd_AdressCode, Nothing)),
        .ReportingDeclarantEORINumber = FirstNonEmptyStr(If(declarant IsNot Nothing, declarant.ezaAd_TeilnehmerEORI, Nothing),
                                                         If(declarant IsNot Nothing, declarant.ezaAd_AdressCode, Nothing)),
        .TypeOfRepresentation = SafeStr(eza.eza_VertretungsVerhaeltnisCode)
      },
      .Commercial = New cATEZ_Greenpulse_KafkaDecs.CommercialNode With {
        .InvoiceNumbers = If(inv IsNot Nothing, SafeStr(inv.ezaUl_Nummer), ""),
        .InvoiceDate = If(inv IsNot Nothing, ToDateYMD(inv.ezaUl_DatumAusstellung), "")
      },
      .ExporterDetails = New cATEZ_Greenpulse_KafkaDecs.ExporterDetailsNode With {
        .ExporterTitle = FirstNonEmptyStr(If(exporter IsNot Nothing, exporter.ezaAd_NameFirma1, Nothing),
                                          If(exporter IsNot Nothing, exporter.ezaAd_NameFirma2, Nothing)),
        .ExporterEmail = "",
        .ExporterPhone = ""
      },
      .ImporterDetails = New cATEZ_Greenpulse_KafkaDecs.ImporterDetailsNode With {
        .ImporterTitle = FirstNonEmptyStr(If(importer IsNot Nothing, importer.ezaAd_NameFirma1, Nothing),
                                          If(importer IsNot Nothing, importer.ezaAd_NameFirma2, Nothing)),
        .ImporterEmail = "",
        .ImporterPhone = "",
        .ImporterCountryCodeOrMemberState = SafeStr(If(importer IsNot Nothing, importer.ezaAd_LandCode, Nothing)),
        .ImporterSubdivision = "",
        .ImporterCity = SafeStr(If(importer IsNot Nothing, importer.ezaAd_Ort, Nothing)),
        .ImporterStreet = MergeStreet(importer),
        .ImporterStreetAdditional = SafeStr(If(importer IsNot Nothing, importer.ezaAd_StrasseHausNr2, Nothing)),
        .ImporterAddressNumber = "",
        .ImporterPostCode = SafeStr(If(importer IsNot Nothing, importer.ezaAd_PLZ, Nothing)),
        .ImporterPoBox = "",
        .ImporterCoordinateLongitudeX = "",
        .ImporterCoordinateLatitudeY = ""
      },
    .Documents = New List(Of cATEZ_Greenpulse_KafkaDecs.DocumentNode)()
    }

        ' --- Falls EORI vorhanden: spezifisch befüllen (Lookup/Mapping via Helper) ---
        Dim impEori As String = SafeStr(If(importer IsNot Nothing, importer.ezaAd_TeilnehmerEORI, Nothing))
        If Not String.IsNullOrWhiteSpace(impEori) Then
            PopulateImporterByEori(impEori, dest.Parties, dest.ImporterDetails)
        End If

        Dim expEori As String = SafeStr(If(exporter IsNot Nothing, exporter.ezaAd_TeilnehmerEORI, Nothing))
        If Not String.IsNullOrWhiteSpace(expEori) Then
            PopulateExporterByEori(expEori, dest.Parties, dest.ExporterDetails)
        End If

        Dim decEori As String = SafeStr(If(declarant IsNot Nothing, declarant.ezaAd_TeilnehmerEORI, Nothing))
        If Not String.IsNullOrWhiteSpace(decEori) Then
            PopulateDeclarantByEori(decEori, dest.Parties)
        End If

        ' --- Warenpositionen -> Goods ---
        If eza.eza_WARENPOS IsNot Nothing Then
            For Each wp In eza.eza_WARENPOS
                Dim gi As New cATEZ_Greenpulse_KafkaDecs.GoodItem With {
          .CommodityCode = SafeStr(wp.ezaWP_WarennummerEZT),
          .OriginCountryCode = SafeStr(wp.ezaWP_UrsprungslandCode),
          .NetMass = ToInvariantStr(wp.ezaWP_Eigenmasse),
          .TypeOfMeasurementUnit = UnitCodeToKafkaName(FirstNonEmptyStr(wp.ezaWP_WarenMasseinheit1, wp.ezaWP_AHStatMengeMasseinheit, "KGM")),
          .SpecialProcedures = New cATEZ_Greenpulse_KafkaDecs.SpecialProceduresNode With {
            .MemberStateAutharization = SafeStr(If(importer IsNot Nothing, importer.ezaAd_LandCode, Nothing)),
            .DischargeBillWaiver = "",
            .Authorisation = FirstNonEmptyStr(wp.ezaWP_Bewilligungsnummer, eza.eza_BewilligungsIDZLAVUV),
            .StartTime = "",
            .EndTime = "",
            .Deadline = ""
          }
        }
                dest.Declaration.Goods.Add(gi)
            Next
        End If

        ' --- Previous/Requested Procedure ggf. aus erster Position ziehen ---
        Dim wp0 = If(eza.eza_WARENPOS IsNot Nothing, eza.eza_WARENPOS.FirstOrDefault(), Nothing)
        If wp0 IsNot Nothing Then
            If String.IsNullOrWhiteSpace(dest.Declaration.RequestedProcedure) Then
                dest.Declaration.RequestedProcedure = SafeStr(wp0.ezaWP_Zollverfahren)
            End If
            dest.Declaration.PreviousProcedure = SafeStr(wp0.ezaWP_VerfahrensCodeVorangegangenesVerfahren)
        End If


        '' --- Dokumente aus Unterlagen übernehmen ---
        'Dim DY As New cDakosy_Zollanmeldungen(eza.eza_dyaAnmID)

        'If DY IsNot Nothing Then
        '    If DY.dy_SendungsId IsNot Nothing Then
        '        Dim ANH_LIST As New List(Of cAvisoAnhaenge)
        '        cAvisoAnhaenge.LOAD_LIST_BySendung(ANH_LIST, DY.dy_SendungsId)


        '        For Each doc In ANH_LIST
        '            Select Case doc.anh_Art
        '                Case "Rechnung", "eFatura"

        '                    Dim dateiBytes As Byte() = System.IO.File.ReadAllBytes(VERAG_PROG_ALLGEMEIN.cDATENSERVER.GET_PDFPath_BY_DocID(doc.anh_docId))

        '                    Dim d As New cATEZ_Greenpulse_KafkaDecs.DocumentNode With {
        '                            .Reference = doc.anh_Name,
        '                            .DocType = "invoice",
        '                            .MimeType = cATEZ_Greenpulse_KafkaDecsBuilder_DAKOSY.GuessMimeTypeFromNumber(doc.anh_Typ),
        '                            .Blob = Convert.ToBase64String(dateiBytes)
        '                        }
        '                    dest.Documents.Add(d)
        '            End Select

        '        Next
        '    End If

        'End If


        Return dest
    End Function

    Public Shared Function BuildByMrnInvoices(eza As cDakosyEZA) As cATEZ_Greenpulse_KafkaInvoices
        If eza Is Nothing Then Throw New ArgumentNullException(NameOf(eza))

        ' Zielobjekt NUR für Dokumente
        Dim dest As New cATEZ_Greenpulse_KafkaInvoices() With {
        .Documents = New List(Of cATEZ_Greenpulse_KafkaInvoices.DocumentNode)()
    }

        ' --- Dokumente aus Unterlagen / Aviso übernehmen ---
        Dim DY As New cDakosy_Zollanmeldungen(eza.eza_dyaAnmID)

        If DY IsNot Nothing AndAlso DY.dy_SendungsId IsNot Nothing Then
            Dim sendungsId As Integer
            If Integer.TryParse(DY.dy_SendungsId.ToString(), sendungsId) AndAlso sendungsId > 0 Then
                dest = BuildInvoices(sendungsId)
            End If
        End If

        Return dest
    End Function

    Public Shared Function BuildInvoices(sendungsId As Integer) As cATEZ_Greenpulse_KafkaInvoices


        ' Zielobjekt NUR für Dokumente
        Dim dest As New cATEZ_Greenpulse_KafkaInvoices() With {
        .Documents = New List(Of cATEZ_Greenpulse_KafkaInvoices.DocumentNode)()
    }



        If sendungsId > 0 Then

            Dim ANH_LIST As New List(Of cAvisoAnhaenge)
            cAvisoAnhaenge.LOAD_LIST_BySendung(ANH_LIST, sendungsId)

            For Each doc In ANH_LIST
                Select Case doc.anh_Art
                    Case "Rechnung", "eFatura"

                        Dim pfad As String = VERAG_PROG_ALLGEMEIN.cDATENSERVER.GET_PDFPath_BY_DocID(doc.anh_docId)
                        If (File.Exists(pfad)) Then

                            Dim dateiBytes As Byte() = System.IO.File.ReadAllBytes(pfad)

                            Dim d As New cATEZ_Greenpulse_KafkaInvoices.DocumentNode With {
                            .Reference = doc.anh_Name,
                            .DocType = "invoice",
                            .MimeType = cATEZ_Greenpulse_KafkaDecsBuilder_DAKOSY.GuessMimeTypeFromNumber(doc.anh_Typ),
                            .Blob = Convert.ToBase64String(dateiBytes)
                        }

                            dest.Documents.Add(d)
                        End If
                End Select
            Next
        End If

        Return dest
    End Function

    Public Shared Sub INSERT_KAFKA(za As cVERAG_CustomsDeclarations)
        Try
            If VERAG_PROG_ALLGEMEIN.cAllgemein.PARAMS Is Nothing Then
                VERAG_PROG_ALLGEMEIN.cAllgemein.PARAMS = New cParameterList(VERAG_PROG_ALLGEMEIN.cAllgemein.PROGID)
            End If
            If VERAG_PROG_ALLGEMEIN.cAllgemein.PARAMS.GET_PARAMETER_VALUE_BOOL("CBAM_INSERT_KAFKA") Then
                Dim key = cATEZ_Greenpulse_KafkaDecs.GetUniqueKey_Pipe_FromVERAG_CustomsDec(za, za.za_MRN)
                Dim rec = cDakosyEZA_Greenpulse.BuildByMrn(za)
                Dim dr = cATEZ_Greenpulse_KafkaDecs.InsertOrUpdateToKafkaSync_Bool(rec, key, 30000)
                'Unlerlagen
                If za.za_SendungsId IsNot Nothing AndAlso za.za_SendungsId > 0 Then
                    Dim rec_inv = cDakosyEZA_Greenpulse.BuildInvoices(za.za_SendungsId)
                    Dim dr_inv = cATEZ_Greenpulse_KafkaInvoices.InsertOrUpdateToKafkaSync_Bool(rec_inv, key, 30000)
                End If
            End If

        Catch ex As Exception
            Try
                cErrorHandler.ERR(ex.Message, ex.StackTrace & vbNewLine & vbNewLine & za.za_MRN, Reflection.MethodInfo.GetCurrentMethod.Name)
            Catch ex2 As Exception
                cErrorHandler.ERR(ex.Message, ex.StackTrace, Reflection.MethodInfo.GetCurrentMethod.Name)
            End Try
        End Try
    End Sub
    Public Shared Function BuildByMrn(za As cVERAG_CustomsDeclarations) As cATEZ_Greenpulse_KafkaDecs
        If za Is Nothing Then Throw New ArgumentNullException(NameOf(za))

        ' --- Parties (Heuristik über zaParty_Role) ---
        Dim importer = GuessPartyByRole(za.Parties, cVERAG_CustomsDeclarations.IMPORTER_ROLES)
        Dim exporter = GuessPartyByRole(za.Parties, cVERAG_CustomsDeclarations.EXPORTER_ROLES)
        Dim declarant = GuessPartyByRole(za.Parties, cVERAG_CustomsDeclarations.DECLARANT_ROLES)

        ' --- Rechnungsdaten (Header-Dokument N380) ---
        Dim inv = If(za.Documents IsNot Nothing,
                 za.Documents.FirstOrDefault(Function(d) _
                     SafeStr(d.zaDoc_Code).Equals("N380", StringComparison.OrdinalIgnoreCase) _
                     AndAlso Not String.IsNullOrWhiteSpace(SafeStr(d.zaDoc_Reference))),
                 Nothing)

        ' --- Zielobjekt ---
        Dim dest As New cATEZ_Greenpulse_KafkaDecs With {
        .Declaration = New cATEZ_Greenpulse_KafkaDecs.DeclarationNode With {
            .DeclarationSourceId = SafeStr(za.za_Id),
            .DeclarationNo = FirstNonEmptyStr(za.za_MRN, za.za_DeclarationNo),
            .DeclarationDate = ToDateYMD(FirstNonEmpty(za.za_DeclarationDate, za.za_ReleaseDate, za.za_AcceptanceDate)),
            .RequestedProcedure = SafeStr(za.za_MainProcedure),
            .PreviousProcedure = "",
            .Goods = New List(Of cATEZ_Greenpulse_KafkaDecs.GoodItem)()
        },
        .Parties = New cATEZ_Greenpulse_KafkaDecs.PartiesNode With {
            .ImporterIdentificationNumber = FirstNonEmptyStr(
                If(importer IsNot Nothing, importer.zaParty_EORI, Nothing),
                If(importer IsNot Nothing, importer.zaParty_TIN, Nothing)
            ),
            .ExporterIdentificationNumber = FirstNonEmptyStr(
                If(exporter IsNot Nothing, exporter.zaParty_EORI, Nothing),
                If(exporter IsNot Nothing, exporter.zaParty_TIN, Nothing)
            ),
            .TypeOfRepresentation = SafeStr(za.za_RepresentationCode)
        },
        .Commercial = New cATEZ_Greenpulse_KafkaDecs.CommercialNode With {
            .InvoiceNumbers = If(inv IsNot Nothing, SafeStr(inv.zaDoc_Reference), ""),
            .InvoiceDate = If(inv IsNot Nothing, ToDateYMD(inv.zaDoc_Date), "")
        },
        .ExporterDetails = New cATEZ_Greenpulse_KafkaDecs.ExporterDetailsNode With {
            .ExporterTitle = If(exporter IsNot Nothing, SafeStr(exporter.zaParty_Name), ""),
            .ExporterEmail = If(exporter IsNot Nothing, SafeStr(exporter.zaParty_Email), ""),
            .ExporterPhone = If(exporter IsNot Nothing, SafeStr(exporter.zaParty_Phone), "")
        },
        .ImporterDetails = New cATEZ_Greenpulse_KafkaDecs.ImporterDetailsNode With {
            .ImporterTitle = If(importer IsNot Nothing, SafeStr(importer.zaParty_Name), ""),
            .ImporterEmail = If(importer IsNot Nothing, SafeStr(importer.zaParty_Email), ""),
            .ImporterPhone = If(importer IsNot Nothing, SafeStr(importer.zaParty_Phone), ""),
            .ImporterCountryCodeOrMemberState = If(importer IsNot Nothing, SafeStr(importer.zaParty_Country), ""),
            .ImporterSubdivision = "",
            .ImporterCity = If(importer IsNot Nothing, SafeStr(importer.zaParty_City), ""),
            .ImporterStreet = If(importer IsNot Nothing, SafeStr(importer.zaParty_Street), ""),
            .ImporterStreetAdditional = "",
            .ImporterAddressNumber = "",
            .ImporterPostCode = If(importer IsNot Nothing, SafeStr(importer.zaParty_PostalCode), ""),
            .ImporterPoBox = "",
            .ImporterCoordinateLongitudeX = "",
            .ImporterCoordinateLatitudeY = ""
        },
        .Documents = New List(Of cATEZ_Greenpulse_KafkaDecs.DocumentNode)()
    }

        Select Case za.za_System
            Case "DAKOSY"
                Select Case If(za.za_RepresentationCode, "")
                    Case "2" : dest.Parties.ReportingDeclarantEORINumber = If(declarant IsNot Nothing, declarant.zaParty_EORI, Nothing) ' '2=indirekt
                    Case Else : dest.Parties.ReportingDeclarantEORINumber = If(importer IsNot Nothing, importer.zaParty_EORI, Nothing) ' direkt
                End Select
            Case "DHF", "DHF_UNISPED"
                Select Case If(za.za_RepresentationCode, "")
                    Case "3" : dest.Parties.ReportingDeclarantEORINumber = If(declarant IsNot Nothing, declarant.zaParty_EORI, Nothing) ' 'indirekt
                    Case "5"
                        If If(za.za_ApplicantTaxOffice, "") = "73001" Then : dest.Parties.ReportingDeclarantEORINumber = If(declarant IsNot Nothing, declarant.zaParty_EORI, Nothing) ' 'indirekt '!!!!!!!!!!!!!!!!!!!!!! za_ApplicantTaxOffice stimmt nciht!!!!!
                        Else : dest.Parties.ReportingDeclarantEORINumber = If(importer IsNot Nothing, importer.zaParty_EORI, Nothing) : End If ' direkt
                    Case Else : dest.Parties.ReportingDeclarantEORINumber = If(importer IsNot Nothing, importer.zaParty_EORI, Nothing) ' direkt
                End Select
            Case "TELOTEC"
                Select Case If(za.za_RepresentationCode, "")
                    Case "3" : dest.Parties.ReportingDeclarantEORINumber = If(declarant IsNot Nothing, declarant.zaParty_EORI, Nothing) ' '3=indirekt
                    Case "5"
                        If If(za.za_ApplicantTaxOffice, "") = "73001" Then : dest.Parties.ReportingDeclarantEORINumber = If(declarant IsNot Nothing, declarant.zaParty_EORI, Nothing) ' 'indirekt '!!!!!!!!!!!!!!!!!!!!!! za_ApplicantTaxOffice stimmt nciht!!!!!
                        Else : dest.Parties.ReportingDeclarantEORINumber = If(importer IsNot Nothing, importer.zaParty_EORI, Nothing) : End If ' direkt
                    Case Else : dest.Parties.ReportingDeclarantEORINumber = If(importer IsNot Nothing, importer.zaParty_EORI, Nothing) ' direkt
                End Select
        End Select


        ' --- EORI-Lookups (optional, wie bisher) ---
        Dim impEori As String = SafeStr(If(importer IsNot Nothing, importer.zaParty_EORI, Nothing))
        If Not String.IsNullOrWhiteSpace(impEori) Then
            PopulateImporterByEori(impEori, dest.Parties, dest.ImporterDetails)
        End If

        Dim expEori As String = SafeStr(If(exporter IsNot Nothing, exporter.zaParty_EORI, Nothing))
        If Not String.IsNullOrWhiteSpace(expEori) Then
            PopulateExporterByEori(expEori, dest.Parties, dest.ExporterDetails)
        End If

        Dim decEori As String = SafeStr(If(declarant IsNot Nothing, declarant.zaParty_EORI, Nothing))
        If Not String.IsNullOrWhiteSpace(decEori) Then
            PopulateDeclarantByEori(decEori, dest.Parties)
        End If

        ' --- Warenpositionen -> Goods ---
        If za.Items IsNot Nothing Then
            For Each it In za.Items
                Dim gi As New cATEZ_Greenpulse_KafkaDecs.GoodItem With {
                .CommodityCode = SafeStr(it.zaItem_HSCode),
                .OriginCountryCode = SafeStr(it.zaItem_OriginCountry),
                .NetMass = ToInvariantStr(it.zaItem_NetMass),
                .TypeOfMeasurementUnit = UnitCodeToKafkaName("KGM"),
                .SpecialProcedures = New cATEZ_Greenpulse_KafkaDecs.SpecialProceduresNode With {
                    .MemberStateAutharization = SafeStr(If(importer IsNot Nothing, importer.zaParty_Country, Nothing)),
                    .DischargeBillWaiver = "",
                    .Authorisation = "",
                    .StartTime = "",
                    .EndTime = "",
                    .Deadline = ""
                }
            }
                dest.Declaration.Goods.Add(gi)
            Next

        End If

        ' --- Requested/Previous Procedure Fallback aus erster Position ---
        Dim it0 = If(za.Items IsNot Nothing, za.Items.FirstOrDefault(), Nothing)
        If it0 IsNot Nothing Then
            If String.IsNullOrWhiteSpace(dest.Declaration.RequestedProcedure) Then
                dest.Declaration.RequestedProcedure = SafeStr(it0.zaItem_MainProcedure)
            End If
            dest.Declaration.PreviousProcedure = SafeStr(it0.zaItem_PrevProcedure)
        End If
        Try
            Console.WriteLine(JsonConvert.SerializeObject(dest, Newtonsoft.Json.Formatting.Indented))
        Catch ex As Exception
            Console.WriteLine("Err JsonConvert" & ex.Message)
        End Try

        Return dest
    End Function

    Private Shared Function GuessPartyByRole(parties As List(Of cVERAG_CustomsDeclarations_Parties),
                                        roles As IEnumerable(Of String)) _
                                        As cVERAG_CustomsDeclarations_Parties
        If parties Is Nothing Then Return Nothing
        Return parties.FirstOrDefault(Function(p) roles.Any(
        Function(r) SafeStr(p.zaParty_Role).Equals(r, StringComparison.OrdinalIgnoreCase)))
    End Function





    ' =========================
    ' Populate-Helper (mit TODO)
    ' =========================

    ' Importer: ergänzt Parties.ImporterIdentificationNumber und ImporterDetails anhand EORI
    Private Shared Sub PopulateImporterByEori(eori As String,
                                           ByRef parties As cATEZ_Greenpulse_KafkaDecs.PartiesNode,
                                           ByRef details As cATEZ_Greenpulse_KafkaDecs.ImporterDetailsNode)
        ' Pflicht: EORI in Parties
        parties.ImporterIdentificationNumber = eori
        Dim AD As VERAG_PROG_ALLGEMEIN.cAdressen = cZOLL_IMPORT.getAdresseFromEORI(eori, "", "", True)

        If AD IsNot Nothing Then
            details.ImporterTitle = (If(AD.Name_1, "") & " " & If(AD.Name_2, "")).trim
            details.ImporterCountryCodeOrMemberState = VERAG_PROG_ALLGEMEIN.cProgramFunctions.getISO2Land(AD.LandKz)
            details.ImporterCity = AD.Ort
            details.ImporterStreet = AD.Straße
            details.ImporterAddressNumber = ""
            details.ImporterPostCode = AD.PLZ
            details.ImporterEmail = AD.E_Mail
            details.ImporterPhone = AD.Telefon
        End If
    End Sub

    ' Exporter: ergänzt Parties.ExporterIdentificationNumber und ExporterDetails anhand EORI
    Private Shared Sub PopulateExporterByEori(eori As String,
                                           ByRef parties As cATEZ_Greenpulse_KafkaDecs.PartiesNode,
                                           ByRef details As cATEZ_Greenpulse_KafkaDecs.ExporterDetailsNode)
        parties.ExporterIdentificationNumber = eori
        Dim AD As VERAG_PROG_ALLGEMEIN.cAdressen = cZOLL_IMPORT.getAdresseFromEORI(eori, "", "", True)

        If AD IsNot Nothing Then
            details.ExporterTitle = (If(AD.Name_1, "") & " " & If(AD.Name_2, "")).trim
            'details.exporterCountryCodeOrMemberState = VERAG_PROG_ALLGEMEIN.cProgramFunctions.getISO2Land(AD.LandKz)
            'details.exporterCity = AD.Ort
            'details.exporterStreet = AD.Straße
            'details.exporterAddressNumber = ""
            'details.exporterPostCode = AD.PLZ
            details.ExporterEmail = AD.E_Mail
            details.ExporterPhone = AD.Telefon
        End If
    End Sub

    ' Declarant: ergänzt Parties.ReportingDeclarantEORINumber (weitere Daten optional)
    Private Shared Sub PopulateDeclarantByEori(eori As String,
                                            ByRef parties As cATEZ_Greenpulse_KafkaDecs.PartiesNode)
        parties.ReportingDeclarantEORINumber = eori

        ' TODO: Optional Stammdaten-Lookup für weitere deklarantenbezogene Daten,
        ' z. B. Standard-Vertretungsverhältnis, Niederlassungsnummer etc.
        ' Dim m = PartnerRepo.GetByEori(eori)
        ' If m IsNot Nothing Then
        '   ' ggf. parties.TypeOfRepresentation = m.DefaultRepType
        ' End If
    End Sub

    ' JSON-Wrapper
    Public Shared Function BuildJsonByMrn(eza As cDakosyEZA, Optional pretty As Boolean = True) As String
        Dim obj = BuildByMrn(eza)
        Return obj.ToJson(pretty)
    End Function

    ' -----------------------------
    ' Helper (ggf. einmalig zentral)
    ' -----------------------------
    Private Shared Function GuessAddressByType(list As List(Of cDakosy_EZA_Adressen), types As IEnumerable(Of String)) As cDakosy_EZA_Adressen
        If list Is Nothing Then Return Nothing
        Dim setTypes = New HashSet(Of String)(types.Select(Function(t) t.ToUpperInvariant()))
        Dim hit = list.FirstOrDefault(Function(a) setTypes.Contains(SafeStr(a.ezaAd_AdressTyp).ToUpperInvariant()))
        If hit IsNot Nothing Then Return hit
        Dim hitEori = list.FirstOrDefault(Function(a) Not String.IsNullOrWhiteSpace(SafeStr(a.ezaAd_TeilnehmerEORI)))
        If hitEori IsNot Nothing Then
            Return hitEori
        Else
            Return list.FirstOrDefault()
        End If
    End Function

    Private Shared Function MergeStreet(addr As cDakosy_EZA_Adressen) As String
        If addr Is Nothing Then Return ""
        Dim s1 = SafeStr(addr.ezaAd_StrasseHausNr1)
        Dim s2 = SafeStr(addr.ezaAd_StrasseHausNr2)
        If s2 <> "" Then Return (s1 & " " & s2).Trim()
        Return s1
    End Function

    Private Shared Function UnitCodeToKafkaName(code As String) As String
        Select Case SafeStr(code).ToUpperInvariant()
            Case "KGM" : Return "Kilograms"
            Case "TNE", "T" : Return "Tonnes"
            Case "LTR" : Return "Litres"
            Case "NAR", "NMB" : Return "Number of items"
            Case Else : Return code
        End Select
    End Function

    Private Shared Function ToDateYMD(value As Object) As String
        If value Is Nothing Then Return ""
        Dim dt As DateTime
        If DateTime.TryParse(SafeStr(value), dt) Then
            Return dt.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)
        End If
        Return ""
    End Function

    Private Shared Function ToInvariantStr(value As Object) As String
        If value Is Nothing Then Return ""
        If TypeOf value Is IFormattable Then
            Return DirectCast(value, IFormattable).ToString(Nothing, CultureInfo.InvariantCulture)
        End If
        Return Convert.ToString(value, CultureInfo.InvariantCulture)
    End Function

    Private Shared Function SafeStr(o As Object) As String
        If o Is Nothing Then Return ""
        Dim s = Convert.ToString(o).Trim()
        Return If(s, "")
    End Function

    Private Shared Function FirstNonEmpty(ParamArray values() As Object) As Object
        For Each v In values
            If v IsNot Nothing AndAlso Not String.IsNullOrWhiteSpace(SafeStr(v)) Then
                Return v
            End If
        Next
        Return Nothing
    End Function

    Private Shared Function FirstNonEmptyStr(ParamArray values() As Object) As String
        For Each v In values
            Dim s = SafeStr(v)
            If Not String.IsNullOrWhiteSpace(s) Then Return s
        Next
        Return ""
    End Function

End Class
