Imports DigitalData.Modules.Interfaces.ZUGFeRDInterface
Imports DigitalData.Modules.Logging

Public Class Validator
    Private ReadOnly _logConfig As LogConfig
    Private ReadOnly _logger As Logger

    Public Sub New(pLogConfig As LogConfig)
        _logConfig = pLogConfig
        _logger = pLogConfig.GetLogger()
    End Sub

    Public Function ValidateZUGFeRDDocument(pResult As ZugferdResult) As ZugferdResult
        ValidateDecimalNodes(pResult)
        ValidateCurrencyNodes(pResult)

        'TODO Validate Datumsfelder
        Return pResult
    End Function

    Private Sub ValidateDecimalNodes(ByRef pResult As ZugferdResult)
        Try
            Dim oDecimalNodes = pResult.XElementObject.Descendants().
                Where(Function(n) n.Name.ToString.EndsWith("Amount") Or n.Name.ToString.EndsWith("Percent"))

            For Each oNode As XElement In oDecimalNodes
                Dim oParsedValue As Decimal = 0.0
                If Decimal.TryParse(oNode.Value, oParsedValue) = False Then
                    pResult.ValidationErrors.Add(New ZugferdValidationError() With {
                    .ElementName = oNode.Name.LocalName,
                    .ElementValue = oNode.Value,
                    .ErrorMessage = "Value could not be parsed as Decimal.",
                    .ErrorMessageDE = "Der Wert ist keine Dezimalzahl.",
                    .ErrorMessageToken = "NoDecimalText"
                })
                End If
            Next

        Catch ex As Exception
            _logger.Error(ex)
        End Try
    End Sub

    Private Sub ValidateCurrencyNodes(ByRef pResult As ZugferdResult)
        ' CurrencyCode Nodes
        Try
            Dim oCurrencyCodeNodes = pResult.XElementObject.Descendants().
                Where(Function(n) n.Name.ToString.EndsWith("CurrencyCode"))

            For Each oNode As XElement In oCurrencyCodeNodes
                Dim oValid = ValidateCurrencyCode(oNode.Value)
                If oValid = False Then
                    pResult.ValidationErrors.Add(New ZugferdValidationError() With {
                        .ElementName = oNode.Name.LocalName,
                        .ElementValue = oNode.Value,
                        .ErrorMessage = "Invalid CurrencyCode. Only 3-Character codes are allowed.",
                        .ErrorMessageDE = "Ungültiger Währungscode. Es sind nur 3-stellige Codes erlaubt.",
                        .ErrorMessageToken = "WrongCurrencyCodeText"
                    })
                End If
            Next
        Catch ex As Exception
            _logger.Error(ex)
        End Try

        ' currencyID
        Try
            Dim oCurrencyIDNodes = pResult.XElementObject.Descendants().
                Where(Function(n) n.Attributes.Any(Function(a) a.Name.LocalName = "currencyID"))

            For Each oNode As XElement In oCurrencyIDNodes
                Dim oCurrencyID As String = oNode.Attribute("currencyID")?.Value

                ' CurrencyID is optional per spec
                If String.IsNullOrWhiteSpace(oCurrencyID) Then
                    Continue For
                End If

                Dim oValid = ValidateCurrencyCode(oCurrencyID)
                If oValid = False Then
                    pResult.ValidationErrors.Add(New ZugferdValidationError() With {
                        .ElementName = oNode.Name.LocalName,
                        .ElementValue = oCurrencyID,
                        .ErrorMessage = "Invalid currencyID. Only 3-Character codes or empty values are allowed.",
                        .ErrorMessageDE = "Ungültige WährungsID. Es sind nur 3-Zeichen lange Codes oder ein leerer Wert erlaubt.",
                        .ErrorMessageToken = "WrongCurrencyIDText"
                    })
                End If
            Next

        Catch ex As Exception
            _logger.Error(ex)
        End Try
    End Sub

    Private Function ValidateCurrencyCode(pValue As String) As Boolean
        Dim oValueRegex As New Text.RegularExpressions.Regex("[A-Z]{3}")

        If oValueRegex.IsMatch(pValue) = False Then
            Return False
        End If

        Return True
    End Function
End Class
