package mapping

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

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recyclebinmutationv1"
	searchsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/searchv1"
	commonv1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/common/v1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func recycleBinMutationContext(t *testing.T) context.Context {
	t.Helper()
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	t.Cleanup(cancel)
	return ctx
}

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

func TestRecycleBinMutationWireRoundTrip(t *testing.T) {
	revision := uint64(11)
	request := semantic.Request{RecordID: "row-1", MinCatalogRevision: &revision}
	wireRequest, err := ToWireRestoreRecycleBinRequest(recycleBinMutationContext(t), request)
	if err != nil {
		t.Fatal(err)
	}
	roundRequest, err := FromWireRestoreRecycleBinRequest(recycleBinMutationContext(t), wireRequest)
	if err != nil || roundRequest.RecordID != request.RecordID || roundRequest.MinCatalogRevision == nil || *roundRequest.MinCatalogRevision != revision {
		t.Fatalf("request = %#v, %v", roundRequest, err)
	}

	response := semantic.Response{Receipts: recycleBinMutationReceipts(revision)}
	wireResponse, err := ToWireRestoreRecycleBinResponse(request, response)
	if err != nil {
		t.Fatal(err)
	}
	roundResponse, err := FromWireRestoreRecycleBinResponse(request, wireResponse)
	if err != nil || roundResponse.Receipts.CatalogRevision != revision {
		t.Fatalf("response = %#v, %v", roundResponse, err)
	}
}

func TestRecycleBinMutationWireRejectsMalformedShapes(t *testing.T) {
	ctx := recycleBinMutationContext(t)
	if _, err := FromWireRestoreRecycleBinRequest(ctx, nil); err == nil {
		t.Fatal("nil request was accepted")
	}
	request := semantic.Request{RecordID: "row-1"}
	if _, err := FromWireRestoreRecycleBinResponse(request, nil); err == nil {
		t.Fatal("nil response was accepted")
	}
	wrong := &runtimev1.RestoreRecycleBinResponse{Receipts: toWireReceipts(recycleBinMutationReceipts(1), recycleBinCapability)}
	if _, err := FromWireRestoreRecycleBinResponse(request, wrong); err == nil {
		t.Fatal("foreign capability receipts were accepted")
	}
}

func TestPurgeRecycleBinWireRoundTrip(t *testing.T) {
	request := semantic.Request{RecordID: "row-2"}
	wireRequest, err := ToWirePurgeRecycleBinRequest(recycleBinMutationContext(t), request)
	if err != nil {
		t.Fatal(err)
	}
	roundRequest, err := FromWirePurgeRecycleBinRequest(recycleBinMutationContext(t), wireRequest)
	if err != nil || roundRequest.RecordID != request.RecordID {
		t.Fatalf("request = %#v, %v", roundRequest, err)
	}
	wireResponse, err := ToWirePurgeRecycleBinResponse(request, semantic.Response{Receipts: recycleBinMutationReceipts(1)})
	if err != nil {
		t.Fatal(err)
	}
	if _, err := FromWirePurgeRecycleBinResponse(request, wireResponse); err != nil {
		t.Fatalf("response = %v", err)
	}
}

func TestRecycleBinMutationStatusPreservesConflictAndRejectsForeignReceipts(t *testing.T) {
	wireErr := ToRecycleBinMutationStatusError(&semantic.Failure{Category: semantic.FailureConflict})
	if status.Code(wireErr) != codes.Aborted {
		t.Fatalf("conflict status = %s, want Aborted", status.Code(wireErr))
	}
	if selectedRootWireErr := ToStatusError(&semantic.Failure{Category: semantic.FailureConflict}); status.Code(selectedRootWireErr) != codes.Internal {
		t.Fatalf("selected-root encoded mutation-only conflict = %s, want Internal", status.Code(selectedRootWireErr))
	}
	if sharedWireErr := toStatusError(&semantic.Failure{Category: semantic.FailureConflict}, queryCapability); status.Code(sharedWireErr) != codes.Internal {
		t.Fatalf("shared non-conflict capability encoded conflict = %s, want Internal", status.Code(sharedWireErr))
	}
	var round *semantic.Failure
	if err := FromRecycleBinMutationStatusError(wireErr); !errors.As(err, &round) || round.Category != semantic.FailureConflict {
		t.Fatalf("round trip = %#v", err)
	}
	if err := FromStatusError(wireErr); !errors.As(err, &round) || round.Category != semantic.FailureInternal {
		t.Fatalf("selected-root decoded mutation-only conflict = %#v, want internal", err)
	}
	if err := fromStatusError(wireErr, queryCapability); !errors.As(err, &round) || round.Category != semantic.FailureInternal {
		t.Fatalf("shared non-conflict capability decoded conflict = %#v, want internal", err)
	}
	if platformWireErr := ToPlatformStatusError(&semantic.Failure{Category: semantic.FailureConflict}, searchsemantic.CapabilityName); status.Code(platformWireErr) != codes.Internal {
		t.Fatalf("platform encoded mutation-only conflict = %s, want Internal", status.Code(platformWireErr))
	}
	if err := FromPlatformStatusError(wireErr, searchsemantic.CapabilityName); !errors.As(err, &round) || round.Category != semantic.FailureInternal {
		t.Fatalf("platform decoded mutation-only conflict = %#v, want internal", err)
	}
	if err := FromRecycleBinMutationStatusError(status.Error(codes.Aborted, "untyped conflict")); !errors.As(err, &round) || round.Category != semantic.FailureInternal {
		t.Fatalf("untyped conflict = %#v, want internal", err)
	}

	wrong := &commonv1.RuntimeErrorDetail{
		Category: commonv1.ErrorCategory_ERROR_CATEGORY_STALE_STATE,
		Receipts: toWireReceipts(recycleBinMutationReceipts(1), recycleBinCapability),
	}
	withDetail, err := status.New(codes.FailedPrecondition, "stale").WithDetails(wrong)
	if err != nil {
		t.Fatal(err)
	}
	if err := FromRecycleBinMutationStatusError(withDetail.Err()); !errors.As(err, &round) || round.Category != semantic.FailureInternal {
		t.Fatalf("foreign receipt = %#v", err)
	}
}
