# frozen_string_literal: true

RSpec.describe BingAdsRubySdk::Errors::ErrorHandler do
  let(:general_error) { BingAdsRubySdk::Errors::GeneralError }
  let(:subject) { described_class.new(api_response).call }

  def shared_expectations
    expect { subject }.to raise_error do |error|
      expect(error).to be_a(error_class)
      expect(error).to have_attributes(error_attributes)
    end
  end

  context "when there is no fault" do
    let(:api_response) do
      {
        is_migrated: "false",
        nested_partial_errors: ""
      }
    end

    it { is_expected.to be nil }
  end

  context "when there is a fault" do
    let(:api_response) do
      {
        faultcode: "s:Server",
        faultstring:
          "Invalid client data. Check the SOAP fault details",
        detail: detail
      }
    end

    let(:batch_error) do
      {
        batch_error: {
          code: "0000",
          details: "Batch error details",
          error_code: "ErrorCode",
          field_path: "{lpurl}",
          index: "0",
          message: "Batch error message",
          type: "reserved for internal use"
        }
      }
    end
    let(:batch_error_list) { [batch_error] }

    let(:editorial_error_list) do
      [
        # Inherits from BatchError
        batch_error[:batch_error].merge(
          appealable: true,
          disapproved_text: "The text that caused the entity to ...",
          location: "ElementName",
          publisher_country: "New Zealand",
          reason_code: 4
        )
      ]
    end

    let(:operation_errors) do
      [{
        operation_error: {
          code: "4503",
          details: "Invalid API Campaign Criterion Type : 0 on API tier",
          error_code: "TypeInvalid",
          message: "The campaign criterion ..."
        }
      }]
    end

    let(:error_message) do
      "Bing Ads API error - Invalid client data. Check the SOAP fault details"
    end

    context "of type ApiFaultDetail" do
      let(:detail) do
        {
          api_fault_detail: {
            tracking_id: "14f89175-e806-4822-8aa7-32b0c7734e11",
            batch_errors: "",
            operation_errors: operation_errors
          }
        }
      end

      let(:error_attributes) do
        {
          batch_errors: [],
          operation_errors: operation_errors,
          message: "TypeInvalid - The campaign criterion ..."
        }
      end

      let(:error_class) { BingAdsRubySdk::Errors::ApiFaultDetail }

      it("raises an error") { shared_expectations }
    end

    context "of type AdApiFaultDetail" do
      let(:detail) do
        {
          ad_api_fault_detail: {
            tracking_id: "14f89175-e806-4822-8aa7-32b0c7734e11",
            errors: errors
          }
        }
      end

      let(:errors) do
        [{
          ad_api_error: {
            code: "0000",
            detail: "Details about error",
            error_code: "ErrorCode",
            message: "Fault message"
          }
        }]
      end

      let(:error_class) { BingAdsRubySdk::Errors::AdApiFaultDetail }
      let(:error_attributes) do
        {
          errors: errors,
          message: "ErrorCode - Fault message"
        }
      end

      it("raises an error") { shared_expectations }
    end

    context "of type EditorialApiFaultDetail" do
      let(:detail) do
        {
          editorial_api_fault_detail: {
            tracking_id: "14f89175-e806-4822-8aa7-32b0c7734e11",
            batch_errors: batch_error_list,
            editorial_errors: editorial_error_list,
            operation_errors: operation_errors
          }
        }
      end

      let(:error_class) { BingAdsRubySdk::Errors::EditorialApiFaultDetail }

      let(:error_attributes) do
        {
          batch_errors: batch_error_list,
          editorial_errors: editorial_error_list,
          operation_errors: operation_errors,
          message: error_message
        }
      end

      context "when all the lists have errors" do
        let(:error_message) do
          "API raised 3 errors, including: ErrorCode - Batch error message"
        end
        it("raises an error") { shared_expectations }
      end

      context "when some of the lists are empty" do
        let(:batch_error_list) { [] }
        let(:editorial_error_list) { [] }
        let(:error_message) do
          "TypeInvalid - The campaign criterion ..."
        end

        it("raises an error") { shared_expectations }
      end
    end

    context "of type ApiBatchFault" do
      let(:detail) do
        {
          api_batch_fault: {
            tracking_id: "14f89175-e806-4822-8aa7-32b0c7734e11",
            batch_errors: batch_error_list
          }
        }
      end

      let(:error_class) { BingAdsRubySdk::Errors::ApiBatchFault }
      let(:error_attributes) do
        {
          batch_errors: batch_error_list,
          message: "ErrorCode - Batch error message"
        }
      end

      it("raises an error") { shared_expectations }
    end

    context "of type ApiFault" do
      let(:detail) do
        {
          api_fault: {
            tracking_id: "14f89175-e806-4822-8aa7-32b0c7734e11",
            operation_errors: operation_errors
          }
        }
      end

      let(:error_attributes) do
        {
          operation_errors: operation_errors,
          message: "TypeInvalid - The campaign criterion ..."
        }
      end
      let(:error_class) { BingAdsRubySdk::Errors::ApiFault }

      it("raises an error") { shared_expectations }
    end

    context "of type InvalidCredentials" do
      let(:api_response) do
        {
          code: "105",
          detail: nil,
          error_code: "InvalidCredentials",
          message: "Authentication failed. Either supplied ..."
        }
      end

      let(:error_class) { general_error }

      let(:error_attributes) do
        {
          raw_response: api_response,
          message: "InvalidCredentials - #{api_response[:message]}"
        }
      end

      it("raises an error") { shared_expectations }
    end

    context "of an unknown type" do
      let(:detail) do
        {
          new_fault_unknown_to_sdk: {
            tracking_id: "14f89175-e806-4822-8aa7-32b0c7734e11",
            new_field: "value"
          }
        }
      end

      let(:error_class) { general_error }

      let(:error_attributes) do
        {
          raw_response: api_response,
          message: error_message
        }
      end

      it("raises an error") { shared_expectations }
    end

    context "when there are no details" do
      let(:detail) { nil }
      let(:error_class) { general_error }
      let(:error_attributes) do
        {
          raw_response: api_response,
          message: error_message
        }
      end

      it("raises an error") { shared_expectations }
    end

    context "when there is no error_code" do
      let(:detail) do
        {
          api_fault: {
            tracking_id: "14f89175-e806-4822-8aa7-32b0c7734e11",
            batch_errors: "",
            operation_errors: {
              operation_error: {
                code: "1001",
                details: "",
                message: "The user is not authorized to perform this action."
              }
            }
          }
        }
      end
      let(:error_class) { BingAdsRubySdk::Errors::ApiFault }
      let(:error_attributes) do
        {
          raw_response: api_response,
          message: "The user is not authorized to perform this action."
        }
      end

      it("raises an error") { shared_expectations }
    end
  end

  context "when there is a deserialization error" do
    let(:api_response) do
      {
        faultcode: "a:DeserializationFailed",
        faultstring: "The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter https://bingads.microsoft.com/CampaignManagement/v11:CampaignId. The InnerException message was 'There was an error deserializing the object of type System.Int64. The value '' cannot be parsed as the type 'Int64'.'.  Please see InnerException for more details.",
        detail: {
          exception_detail: {
            help_link: nil,
            inner_exception: {
              help_link: nil,
              inner_exception: {
                help_link: nil,
                inner_exception: {
                  help_link: nil,
                  inner_exception: nil,
                  message: "Input string was not in a correct format.",
                  stack_trace: "   at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)\r\n   at System.Number.ParseInt64(String value, NumberStyles options, NumberFormatInfo numfmt)\r\n   at System.Xml.XmlConverter.ToInt64(String value)",
                  type: "System.FormatException"
                },
                message: "The value '' cannot be parsed as the type 'Int64'.",
                stack_trace: "   at System.Xml.XmlConverter.ToInt64(String value)\r\n   at System.Xml.XmlDictionaryReader.ReadElementContentAsLong()\r\n   at System.Runtime.Serialization.LongDataContract.ReadXmlValue(XmlReaderDelegator reader, XmlObjectSerializerReadContext context)\r\n   at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContractResolver)",
                type: "System.Xml.XmlException"
              },
              message: "There was an error deserializing the object of type System.Int64. The value '' cannot be parsed as the type 'Int64'.",
              stack_trace: "   at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContractResolver)\r\n   at System.Runtime.Serialization.DataContractSerializer.ReadObject(XmlDictionaryReader reader, Boolean verifyObjectName)\r\n   at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.PartInfo.ReadObject(XmlDictionaryReader reader, XmlObjectSerializer serializer)\r\n   at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeParameterPart(XmlDictionaryReader reader, PartInfo part, Boolean isRequest)",
              type: "System.Runtime.Serialization.SerializationException"
            },
            message: "The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter https://bingads.microsoft.com/CampaignManagement/v11:CampaignId. The InnerException message was 'There was an error deserializing the object of type System.Int64. The value '' cannot be parsed as the type 'Int64'.'.  Please see InnerException for more details.",
            stack_trace: "   at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeParameterPart(XmlDictionaryReader reader, PartInfo part, Boolean isRequest)\r\n   at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeParameters(XmlDictionaryReader reader, PartInfo[] parts, Object[] parameters, Boolean isRequest)\r\n   at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeBody(XmlDictionaryReader reader, MessageVersion version, String action, MessageDescription messageDescription, Object[] parameters, Boolean isRequest)\r\n   at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeBodyContents(Message message, Object[] parameters, Boolean isRequest)\r\n   at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeRequest(Message message, Object[] parameters)\r\n   at System.ServiceModel.Dispatcher.DispatchOperationRuntime.DeserializeInputs(MessageRpc& rpc)\r\n   at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)\r\n   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)\r\n   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc)\r\n   at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)",
            type: "System.ServiceModel.Dispatcher.NetDispatcherFaultException"
          }
        }
      }
    end

    let(:error_class) { BingAdsRubySdk::Errors::GeneralError }
    let(:error_attributes) do
      {
        raw_response: api_response,
        message: "Bing Ads API error - The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter https://bingads.microsoft.com/CampaignManagement/v11:CampaignId. The InnerException message was 'There was an error deserializing the object of type System.Int64. The value '' cannot be parsed as the type 'Int64'.'.  Please see InnerException for more details."
      }
    end

    it("raises an error") { shared_expectations }
  end

  context "when there are nested partial errors" do
    let(:api_response) do
      {
        campaign_criterion_ids: [],
        is_migrated: "false",
        nested_partial_errors: {
          batch_error_collection: [
            {
              batch_errors: nil,
              code: "1043",
              details: "Criterion already exists",
              error_code: "AlreadyExists",
              field_path: nil,
              forward_compatibility_map: nil,
              index: "0",
              message: "The specified entity already exists.",
              type: "BatchErrorCollection"
            }
          ]
        }
      }
    end

    context "when the default behaviour is used" do
      let(:error_class) { BingAdsRubySdk::Errors::NestedPartialError }
      let(:error_attributes) do
        {
          raw_response: api_response,
          message: "AlreadyExists - The specified entity already exists."
        }
      end

      it("raises an error") { shared_expectations }
    end

    context "when the ignore partial errors switch is on" do
      it "should return the NestedPartialError as a Hash"
    end
  end

  context "when there are partial errors - multiple batch_errors" do
    let(:api_response) do
      {
        campaign_ids: [],
        partial_errors: {
          batch_error: [
            {
              code: "4701",
              details: nil,
              error_code: "UnsupportedBiddingScheme",
              field_path: nil,
              forward_compatibility_map: nil,
              index: "0",
              message: "The bidding...",
              type: "BatchError"
            },
            {
              code: "4701",
              details: nil,
              error_code: "UnsupportedBiddingScheme",
              field_path: nil,
              forward_compatibility_map: nil,
              index: "1",
              message: "The bidding...",
              type: "BatchError"
            }
          ]
        }
      }
    end

    context "when the default behaviour is used" do
      let(:error_class) { BingAdsRubySdk::Errors::PartialError }

      let(:error_attributes) do
        {
          raw_response: api_response,
          message: "API raised 2 errors, including: UnsupportedBiddingScheme - The bidding..."
        }
      end

      it("raises an error") { shared_expectations }
    end

    context "when the ignore partial errors switch is on" do
      it "should return the PartialError as a Hash"
    end
  end

  context "when there are partial errors - one batch_error" do
    let(:api_response) do
      {
        campaign_ids: [],
        partial_errors: {
          batch_error: {
            code: "4701",
            details: nil,
            error_code: "UnsupportedBiddingScheme",
            field_path: nil,
            forward_compatibility_map: nil,
            index: "0",
            message: "The bidding...",
            type: "BatchError"
          }
        }
      }
    end

    context "when the default behaviour is used" do
      let(:error_class) { BingAdsRubySdk::Errors::PartialError }

      let(:error_attributes) do
        {
          raw_response: api_response,
          message: "UnsupportedBiddingScheme - The bidding..."
        }
      end

      it("raises an error") { shared_expectations }

      it "contains a collection of batch_error" do
        expect { subject }.to raise_error do |error|
          expect(error).to respond_to(:batch_error)
          expect(error.batch_error).to be_a(Array)
          expect(error.batch_error.size).to eq(1)
        end
      end
    end
  end
end