package mapping

import (
	"context"
	"errors"
	"reflect"
	"testing"
	"time"

	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/listviewv1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"google.golang.org/protobuf/encoding/protowire"
	"google.golang.org/protobuf/proto"
)

func TestPrivateListViewMappingPreservesSelectorsPatchPresenceAndReceipts(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	emptyFields := &semantic.FieldSelection{FieldIDs: []string{}}
	sortField := "Name"
	direction := semantic.SortAscending
	request := semantic.MutateRequest{
		Operation: semantic.MutationUpdate, ObjectID: "Account", ViewID: "view-1",
		Patch: &semantic.Patch{Fields: emptyFields, SortFieldID: &sortField, SortDirection: &direction},
	}
	wireRequest, err := ToWireMutatePrivateListViewRequest(ctx, request)
	if err != nil {
		t.Fatal(err)
	}
	roundRequest, err := FromWireMutatePrivateListViewRequest(ctx, wireRequest)
	if err != nil || !reflect.DeepEqual(roundRequest, request) {
		t.Fatalf("request round trip = %#v, %v", roundRequest, err)
	}
	if roundRequest.Patch.Fields == nil || len(roundRequest.Patch.Fields.FieldIDs) != 0 {
		t.Fatalf("empty field-selection presence lost: %#v", roundRequest.Patch)
	}

	view := semantic.View{
		ViewID: "view-1", ObjectID: "Account", Label: "My accounts", FieldIDs: []string{"Name"},
		SortFieldID: "Name", SortDirection: semantic.SortAscending, StableName: "My_Accounts",
	}
	response := semantic.MutateResponse{View: &view, Receipts: listViewTestReceipts()}
	wireResponse, err := ToWireMutatePrivateListViewResponse(request, response)
	if err != nil {
		t.Fatal(err)
	}
	roundResponse, err := FromWireMutatePrivateListViewResponse(request, wireResponse)
	if err != nil || !reflect.DeepEqual(roundResponse, response) {
		t.Fatalf("response round trip = %#v, %v", roundResponse, err)
	}
}

func TestPrivateListViewMappingNCompatibilityRules(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	request := &runtimev1.ReadPrivateListViewsRequest{
		Selector: runtimev1.PrivateListViewReadSelector_PRIVATE_LIST_VIEW_READ_SELECTOR_LIST,
		ObjectId: "Account",
	}
	encoded, err := proto.Marshal(request)
	if err != nil {
		t.Fatal(err)
	}
	encoded = protowire.AppendTag(encoded, 99, protowire.BytesType)
	encoded = protowire.AppendString(encoded, "future-optional")
	var additive runtimev1.ReadPrivateListViewsRequest
	if err := proto.Unmarshal(encoded, &additive); err != nil {
		t.Fatal(err)
	}
	semanticRequest, err := FromWireReadPrivateListViewsRequest(ctx, &additive)
	if err != nil || semanticRequest.Selector != semantic.ReadList || semanticRequest.ObjectID != "Account" {
		t.Fatalf("unknown optional request field = %#v, %v", semanticRequest, err)
	}

	additive.Selector = runtimev1.PrivateListViewReadSelector(99)
	if _, err := FromWireReadPrivateListViewsRequest(ctx, &additive); err == nil {
		t.Fatal("unknown closed selector was accepted")
	}
	mutation := &runtimev1.MutatePrivateListViewRequest{
		Operation: runtimev1.PrivateListViewMutationOperation_PRIVATE_LIST_VIEW_MUTATION_OPERATION_UPDATE,
		ObjectId:  "Account", ViewId: "view-1",
		Patch: &runtimev1.PrivateListViewPatch{SortDirection: privateListViewDirection(runtimev1.PrivateListViewSortDirection(99))},
	}
	if _, err := FromWireMutatePrivateListViewRequest(ctx, mutation); err == nil {
		t.Fatal("unknown closed patch enum was accepted")
	}

	semanticListRequest := semantic.ReadRequest{Selector: semantic.ReadList, ObjectID: "Account"}
	wireResponse, err := ToWireReadPrivateListViewsResponse(semanticListRequest, semantic.ReadResponse{
		Views:    []semantic.View{{ViewID: "view-1", ObjectID: "Account", Label: "My accounts", FieldIDs: []string{"Name"}, StableName: "My_Accounts"}},
		Receipts: listViewTestReceipts(),
	})
	if err != nil {
		t.Fatal(err)
	}
	encoded, err = proto.Marshal(wireResponse)
	if err != nil {
		t.Fatal(err)
	}
	encoded = protowire.AppendTag(encoded, 99, protowire.BytesType)
	encoded = protowire.AppendString(encoded, "future-optional")
	var additiveResponse runtimev1.ReadPrivateListViewsResponse
	if err := proto.Unmarshal(encoded, &additiveResponse); err != nil {
		t.Fatal(err)
	}
	roundResponse, err := FromWireReadPrivateListViewsResponse(semanticListRequest, &additiveResponse)
	if err != nil || len(roundResponse.Views) != 1 || roundResponse.Views[0].ViewID != "view-1" {
		t.Fatalf("unknown optional response field = %#v, %v", roundResponse, err)
	}
}

func TestPrivateListViewStatusUsesStableSafeTaxonomy(t *testing.T) {
	err := ToPlatformStatusError(&commonsemantic.Failure{
		Category: commonsemantic.FailureInvalidRequest, InvalidComponent: commonsemantic.InvalidComponentSavedViewID,
	}, semantic.CapabilityName)
	var failure *commonsemantic.Failure
	if decoded := FromPlatformStatusError(err, semantic.CapabilityName); !errors.As(decoded, &failure) ||
		failure.Category != commonsemantic.FailureInvalidRequest ||
		failure.InvalidComponent != commonsemantic.InvalidComponentSavedViewID {
		t.Fatalf("decoded status = %#v", decoded)
	}
	if decoded := FromPlatformStatusError(err, "runtime.unknown.v1"); !errors.As(decoded, &failure) ||
		failure.Category != commonsemantic.FailureInternal {
		t.Fatalf("cross-capability status = %#v", decoded)
	}
}

func privateListViewDirection(value runtimev1.PrivateListViewSortDirection) *runtimev1.PrivateListViewSortDirection {
	return &value
}

func listViewTestReceipts() semantic.Receipts {
	return semantic.Receipts{
		ProtocolVersion: "1.0", CatalogRevision: 1, SchemaGeneration: 2,
		EngineGeneration: 3, SecurityEpoch: 4, CapabilityVersion: semantic.CapabilityName,
	}
}
