using ZB.MOM.WW.ScadaBridge.NotificationService.Ews;

namespace ZB.MOM.WW.ScadaBridge.NotificationService.Tests.Ews;

/// <summary>
/// Tests for the pure EWS response parser. The parser reports shape only
/// (success / error / SOAP fault / unparseable) plus the response code — the
/// transient-vs-permanent judgement belongs to the sender's classifier.
/// </summary>
public class EwsResponseParserTests
{
    private const string SuccessBody = """
        <?xml version="1.0" encoding="utf-8"?>
        <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
          <s:Body>
            <m:CreateItemResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
                                  xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
              <m:ResponseMessages>
                <m:CreateItemResponseMessage ResponseClass="Success">
                  <m:ResponseCode>NoError</m:ResponseCode>
                  <m:Items><t:Message><t:ItemId Id="AAA=" ChangeKey="CQ=="/></t:Message></m:Items>
                </m:CreateItemResponseMessage>
              </m:ResponseMessages>
            </m:CreateItemResponse>
          </s:Body>
        </s:Envelope>
        """;

    private const string ErrorBody = """
        <?xml version="1.0" encoding="utf-8"?>
        <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
          <s:Body>
            <m:CreateItemResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
                                  xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
              <m:ResponseMessages>
                <m:CreateItemResponseMessage ResponseClass="Error">
                  <m:MessageText>server busy</m:MessageText>
                  <m:ResponseCode>ErrorServerBusy</m:ResponseCode>
                  <m:DescriptiveLinkKey>0</m:DescriptiveLinkKey>
                </m:CreateItemResponseMessage>
              </m:ResponseMessages>
            </m:CreateItemResponse>
          </s:Body>
        </s:Envelope>
        """;

    private const string FaultWithResponseCodeBody = """
        <?xml version="1.0" encoding="utf-8"?>
        <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
          <s:Body>
            <s:Fault>
              <faultcode xmlns:a="http://schemas.microsoft.com/exchange/services/2006/types">a:ErrorSchemaValidation</faultcode>
              <faultstring xml:lang="en-US">The request failed schema validation.</faultstring>
              <detail>
                <e:ResponseCode xmlns:e="http://schemas.microsoft.com/exchange/services/2006/errors">ErrorSchemaValidation</e:ResponseCode>
                <e:Message xmlns:e="http://schemas.microsoft.com/exchange/services/2006/errors">The request failed schema validation.</e:Message>
              </detail>
            </s:Fault>
          </s:Body>
        </s:Envelope>
        """;

    private const string FaultWithoutResponseCodeBody = """
        <?xml version="1.0" encoding="utf-8"?>
        <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
          <s:Body>
            <s:Fault>
              <faultcode>s:Server</faultcode>
              <faultstring xml:lang="en-US">An internal server error occurred.</faultstring>
              <detail><e:Message xmlns:e="http://schemas.microsoft.com/exchange/services/2006/errors">boom</e:Message></detail>
            </s:Fault>
          </s:Body>
        </s:Envelope>
        """;

    [Fact]
    public void Parse_SuccessResponse_ReturnsSuccessWithNoError()
    {
        var result = EwsResponseParser.Parse(SuccessBody);

        Assert.Equal(EwsResponseKind.Success, result.Kind);
        Assert.Equal("NoError", result.ResponseCode);
    }

    [Fact]
    public void Parse_ErrorResponse_ReturnsErrorWithCodeAndMessage()
    {
        var result = EwsResponseParser.Parse(ErrorBody);

        Assert.Equal(EwsResponseKind.Error, result.Kind);
        Assert.Equal("ErrorServerBusy", result.ResponseCode);
        Assert.Equal("server busy", result.MessageText);
    }

    [Fact]
    public void Parse_SoapFaultWithDetailResponseCode_ReturnsFaultWithCode()
    {
        var result = EwsResponseParser.Parse(FaultWithResponseCodeBody);

        Assert.Equal(EwsResponseKind.Fault, result.Kind);
        Assert.Equal("ErrorSchemaValidation", result.ResponseCode);
        Assert.Equal("The request failed schema validation.", result.MessageText);
    }

    [Fact]
    public void Parse_SoapFaultWithoutResponseCode_ReturnsFaultWithNullCode()
    {
        var result = EwsResponseParser.Parse(FaultWithoutResponseCodeBody);

        Assert.Equal(EwsResponseKind.Fault, result.Kind);
        Assert.Null(result.ResponseCode);
        Assert.Equal("An internal server error occurred.", result.MessageText);
    }

    [Fact]
    public void Parse_PayloadWithDtdEntityDeclaration_IsRejectedAsUnparseable()
    {
        // Pins DTD-prohibited parsing (XXE guard). The response body is external input, so the
        // parser must never process a DOCTYPE. LINQ-to-XML does NOT prohibit DTDs on its own, so
        // the parser reads through an XmlReader pinned to DtdProcessing.Prohibit with a null
        // resolver; a refactor that loosens either setting fails here.
        //
        // The payload is deliberately a well-formed EWS response so the assertion has teeth: with
        // DTDs prohibited the DOCTYPE itself throws and the body is Unparseable, whereas any
        // DtdProcessing.Parse configuration expands the internal entity and yields Kind=Error.
        // The external file:/// entity is declared alongside it as the XXE payload that must
        // likewise never be reached.
        const string xxe = """
            <?xml version="1.0" encoding="utf-8"?>
            <!DOCTYPE Envelope [
              <!ENTITY xxe SYSTEM "file:///etc/passwd">
              <!ENTITY expanded "server busy">
            ]>
            <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
              <s:Body>
                <m:CreateItemResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages">
                  <m:ResponseMessages>
                    <m:CreateItemResponseMessage ResponseClass="Error">
                      <m:MessageText>&expanded;</m:MessageText>
                      <m:ResponseCode>ErrorServerBusy</m:ResponseCode>
                    </m:CreateItemResponseMessage>
                  </m:ResponseMessages>
                </m:CreateItemResponse>
              </s:Body>
            </s:Envelope>
            """;

        var result = EwsResponseParser.Parse(xxe);

        Assert.Equal(EwsResponseKind.Unparseable, result.Kind);
        Assert.Null(result.ResponseCode);
        Assert.Null(result.MessageText);
    }

    [Theory]
    [InlineData("not xml")]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData("<foo/>")]
    public void Parse_GarbageOrUnrelatedPayload_ReturnsUnparseable(string body)
    {
        var result = EwsResponseParser.Parse(body);

        Assert.Equal(EwsResponseKind.Unparseable, result.Kind);
        Assert.Null(result.ResponseCode);
        Assert.Null(result.MessageText);
    }
}
