package smithyrpcv2cborquerycompatible

import (
	"bytes"
	"context"
	"errors"
	"io"
	"net/http"
	"testing"

	smithy "github.com/aws/smithy-go"
	"github.com/aws/smithy-go/encoding/cbor"
	smithyendpoints "github.com/aws/smithy-go/endpoints"
	"github.com/aws/smithy-go/middleware"
	smithyprivateprotocol "github.com/aws/smithy-go/private/protocol"
)

// Implements awsQuery-compatible SEP tests
// (query-protocol-migration-compatibility.md)

type mockHTTP struct {
	QueryError            string
	BodyType, BodyMessage string
}

func (m mockHTTP) Do(r *http.Request) (*http.Response, error) {
	return &http.Response{
		StatusCode: 400,
		Header: http.Header{
			"Smithy-Protocol":    {"rpc-v2-cbor"},
			"X-Amzn-Query-Error": {m.QueryError},
		},
		Body: io.NopCloser(bytes.NewReader(cbor.Encode(cbor.Map{
			"__type":  cbor.String(m.BodyType),
			"message": cbor.String(m.BodyMessage),
		}))),
	}, nil
}

type endpointResolver struct{}

func (*endpointResolver) ResolveEndpoint(ctx context.Context, params EndpointParameters) (smithyendpoints.Endpoint, error) {
	return smithyendpoints.Endpoint{}, nil
}

// TC1: Validate SDK can parse Code field
func TestQueryCompatible_SEP1(t *testing.T) {
	svc := New(Options{
		HTTPClient: mockHTTP{
			QueryError: "AWS.SimpleQueueService.NonExistentQueue;Sender",
			BodyType:   "smithy.protocoltests.rpcv2Cbor#ItemNotFound",
		},
		EndpointResolverV2: &endpointResolver{},
	})
	_, err := svc.GetItem(context.Background(), &GetItemInput{})
	if err == nil {
		t.Fatal("expect err, got none")
	}

	var terr smithy.APIError
	if !errors.As(err, &terr) {
		t.Fatalf("expect smithy.APIError, got %T", err)
	}

	expect := "AWS.SimpleQueueService.NonExistentQueue"
	if actual := terr.ErrorCode(); expect != actual {
		t.Errorf("ErrorCode: %q != %q", expect, actual)
	}
}

// TC2: Validate SDK can handle missing Code field
// (missing X-Amzn-Query-Error)
func TestQueryCompatible_SEP2(t *testing.T) {
	svc := New(Options{
		HTTPClient: mockHTTP{
			BodyType: "smithy.protocoltests.rpcv2Cbor#ItemNotFound",
		},
		EndpointResolverV2: &endpointResolver{},
	})
	_, err := svc.GetItem(context.Background(), &GetItemInput{})
	if err == nil {
		t.Fatal("expect err, got none")
	}

	var terr smithy.APIError
	if !errors.As(err, &terr) {
		t.Fatalf("expect smithy.APIError, got %T", err)
	}

	expect := "ItemNotFound"
	if actual := terr.ErrorCode(); expect != actual {
		t.Errorf("ErrorCode: %q != %q", expect, actual)
	}
}

// TC3: Validate SDK can parse Type field
func TestQueryCompatible_SEP3(t *testing.T) {
	svc := New(Options{
		HTTPClient: mockHTTP{
			QueryError: "AWS.SimpleQueueService.NonExistentQueue;Sender",
			BodyType:   "smithy.protocoltests.rpcv2Cbor#ItemNotFound",
		},
		EndpointResolverV2: &endpointResolver{},
	})
	_, err := svc.GetItem(context.Background(), &GetItemInput{})
	if err == nil {
		t.Fatal("expect err, got none")
	}

	var terr smithy.APIError
	if !errors.As(err, &terr) {
		t.Fatalf("expect smithy.APIError, got %T", err)
	}

	expectCode := "AWS.SimpleQueueService.NonExistentQueue"
	if actual := terr.ErrorCode(); expectCode != actual {
		t.Errorf("ErrorCode: %q != %q", expectCode, actual)
	}

	expectFault := smithy.FaultClient
	if actual := terr.ErrorFault(); expectFault != actual {
		t.Errorf("ErrorFault: %v != %v", expectFault, actual)
	}
}

// TC4: Validate SDK sends x-amzn-query-mode header when service has
// @awsQueryCompatible trait
func TestQueryCompatible_SEP4(t *testing.T) {
	var req http.Request
	svc := New(Options{
		HTTPClient: mockHTTP{},
		APIOptions: []func(*middleware.Stack) error{
			func(s *middleware.Stack) error {
				return smithyprivateprotocol.AddCaptureRequestMiddleware(s, &req)
			},
		},
	})
	svc.GetItem(context.Background(), &GetItemInput{})

	expect := "true"
	if actual := req.Header.Get("X-Amzn-Query-Mode"); expect != actual {
		t.Errorf("X-Amzn-Query-Mode header: %q != %q", expect, actual)
	}
}

// TC5 covered in internal/protocoltest/smithyrpcv2cbor/querycompatible_test.go
