package runtimeapi

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"testing"
	"time"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/permissions"
	sharedErrors "github.com/nexuscrm/shared/pkg/errors"
	catalogsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	filessemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/filesv1"
	layoutsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv2"
	reportsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/reportv1"
	searchsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/searchv1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func TestPlatformEngineClassifierConflictAcrossWire(t *testing.T) {
	conflict := appErrors.NewConflictErrorWithMessage("Layout", "internal detail must not cross the boundary")
	for _, test := range []struct {
		name     string
		err      error
		category commonsemantic.FailureCategory
		code     codes.Code
		reason   commonsemantic.ConflictReason
	}{
		{"conflict", conflict, commonsemantic.FailureConflict, codes.Aborted, ""},
		{"legacy HTTP conflict", legacyHTTPConflict{}, commonsemantic.FailureConflict, codes.Aborted, ""},
		{"transparent wrapper", fmt.Errorf("operation: %w", conflict), commonsemantic.FailureConflict, codes.Aborted, ""},
		{"internal owner", appErrors.NewInternalError("storage", conflict), commonsemantic.FailureInternal, codes.Internal, ""},
		{"ambiguous owners", errors.Join(conflict, appErrors.NewPermissionError("write", "Layout")), commonsemantic.FailureInternal, codes.Internal, ""},
		{"revision mismatch", appErrors.NewConflictErrorWithReason("Layout", commonsemantic.ConflictRevisionMismatch, conflict.Error()), commonsemantic.FailureConflict, codes.Aborted, commonsemantic.ConflictRevisionMismatch},
		{"referenced", appErrors.NewConflictErrorWithReason("Layout", commonsemantic.ConflictReferenced, conflict.Error()), commonsemantic.FailureConflict, codes.Aborted, commonsemantic.ConflictReferenced},
		{"default required", appErrors.NewConflictErrorWithReason("Layout", commonsemantic.ConflictDefaultRequired, conflict.Error()), commonsemantic.FailureConflict, codes.Aborted, commonsemantic.ConflictDefaultRequired},
		{"unknown reason", appErrors.NewConflictErrorWithReason("Layout", "unknown", conflict.Error()), commonsemantic.FailureInternal, codes.Internal, ""},
	} {
		t.Run(test.name, func(t *testing.T) {
			classified := classifyLayoutAuthoringV2Error(context.Background(), test.err)
			wireErr := layoutAuthoringV2Status(classified)
			if got := status.Code(wireErr); got != test.code {
				t.Fatalf("gRPC status = %v, want %v", got, test.code)
			}
			decoded := mapping.FromPlatformStatusError(wireErr, layoutsemantic.CapabilityName)
			var failure *commonsemantic.Failure
			if !errors.As(decoded, &failure) || failure.Category != test.category || failure.ConflictReason != test.reason {
				t.Fatalf("decoded = %#v, want %s", decoded, test.category)
			}
			if strings.Contains(status.Convert(wireErr).Proto().String(), conflict.Error()) {
				t.Fatal("internal conflict detail crossed the wire")
			}
		})
	}
}

type legacyHTTPConflict struct{}

func (legacyHTTPConflict) Error() string   { return "legacy engine conflict" }
func (legacyHTTPConflict) HTTPStatus() int { return 409 }

func TestPlatformEngineClassifierUsesCapabilityApprovedInvalidComponent(t *testing.T) {
	tests := []struct {
		name       string
		capability string
		component  commonsemantic.InvalidComponent
	}{
		{
			name:       "App Metadata",
			capability: catalogsemantic.AppMetadataCapabilityName,
			component:  commonsemantic.InvalidComponentResponse,
		},
		{
			name:       "Integration Binding",
			capability: catalogsemantic.IntegrationBindingCapabilityName,
			component:  commonsemantic.InvalidComponentResponse,
		},
		{
			name:       "Search",
			capability: searchsemantic.CapabilityName,
			component:  commonsemantic.InvalidComponentQueryPlan,
		},
		{
			name:       "Report",
			capability: reportsemantic.CapabilityName,
			component:  commonsemantic.InvalidComponentQueryPlan,
		},
		{
			name:       "Files create",
			capability: filessemantic.CapabilityName,
			component:  commonsemantic.InvalidComponentCommandData,
		},
		{
			name:       "Files record identity",
			capability: filessemantic.CapabilityName,
			component:  commonsemantic.InvalidComponentRecordID,
		},
		{
			name:       "Files response",
			capability: filessemantic.CapabilityName,
			component:  commonsemantic.InvalidComponentResponse,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			wireErr := mapping.ToPlatformStatusError(
				classifyPlatformEngineError(
					context.Background(),
					sharedErrors.NewMalformedRequestError("malformed"),
					test.component,
				),
				test.capability,
			)
			if got := status.Code(wireErr); got != codes.InvalidArgument {
				t.Fatalf("gRPC code = %v, want InvalidArgument", got)
			}
			var failure *commonsemantic.Failure
			decoded := mapping.FromPlatformStatusError(wireErr, test.capability)
			if !errors.As(decoded, &failure) ||
				failure.Category != commonsemantic.FailureInvalidRequest ||
				failure.InvalidComponent != test.component {
				t.Fatalf("decoded failure = %#v, want invalid request component %q", decoded, test.component)
			}
		})
	}
}

func TestPlatformEngineClassifierPreservesNeutralErrorTaxonomy(t *testing.T) {
	retriable := permissions.ErrFieldPermissionProjectionOverloaded
	tests := []struct {
		name     string
		err      error
		category commonsemantic.FailureCategory
		code     codes.Code
	}{
		{
			name:     "invalid reference",
			err:      sharedErrors.NewInvalidReferenceError("invalid reference"),
			category: commonsemantic.FailureInvalidRequest,
			code:     codes.InvalidArgument,
		},
		{
			name:     "field integrity",
			err:      sharedErrors.NewFieldIntegrityError("field integrity"),
			category: commonsemantic.FailureInvalidRequest,
			code:     codes.InvalidArgument,
		},
		{
			name:     "invalid operation",
			err:      sharedErrors.NewInvalidOperationError("invalid operation"),
			category: commonsemantic.FailureInvalidRequest,
			code:     codes.InvalidArgument,
		},
		{
			name:     "invalid field",
			err:      sharedErrors.NewInvalidFieldError("invalid field"),
			category: commonsemantic.FailureInvalidRequest,
			code:     codes.InvalidArgument,
		},
		{
			name:     "malformed request",
			err:      sharedErrors.NewMalformedRequestError("malformed"),
			category: commonsemantic.FailureInvalidRequest,
			code:     codes.InvalidArgument,
		},
		{
			name:     "transparent wrapper preserves malformed request",
			err:      fmt.Errorf("transparent: %w", sharedErrors.NewMalformedRequestError("malformed")),
			category: commonsemantic.FailureInvalidRequest,
			code:     codes.InvalidArgument,
		},
		{
			name:     "domain not found",
			err:      sharedErrors.NewNotFoundWireError("missing"),
			category: commonsemantic.FailureMissing,
			code:     codes.NotFound,
		},
		{
			name:     "typed not found",
			err:      appErrors.NewNotFoundError("File", "missing"),
			category: commonsemantic.FailureMissing,
			code:     codes.NotFound,
		},
		{
			name:     "typed validation",
			err:      appErrors.NewValidationError("File", "invalid"),
			category: commonsemantic.FailureInvalidRequest,
			code:     codes.InvalidArgument,
		},
		{
			name:     "unauthenticated",
			err:      appErrors.NewUnauthorizedError("expired"),
			category: commonsemantic.FailureUnauthenticated,
			code:     codes.Unauthenticated,
		},
		{
			name:     "permission denied",
			err:      appErrors.NewPermissionError("read", "File"),
			category: commonsemantic.FailureForbidden,
			code:     codes.PermissionDenied,
		},
		{
			name:     "retriable",
			err:      retriable,
			category: commonsemantic.FailureUnavailable,
			code:     codes.Unavailable,
		},
		{
			name:     "owner cancellation while request remains live",
			err:      context.Canceled,
			category: commonsemantic.FailureUnavailable,
			code:     codes.Unavailable,
		},
		{
			name:     "owner deadline while request remains live",
			err:      context.DeadlineExceeded,
			category: commonsemantic.FailureUnavailable,
			code:     codes.Unavailable,
		},
		{
			name:     "internal wrapper wins over domain cause",
			err:      appErrors.NewInternalError("storage", sharedErrors.NewNotFoundWireError("missing")),
			category: commonsemantic.FailureInternal,
			code:     codes.Internal,
		},
		{
			name: "joined domain conditions have no single owner",
			err: errors.Join(
				sharedErrors.NewMalformedRequestError("malformed"),
				sharedErrors.NewNotFoundWireError("missing"),
			),
			category: commonsemantic.FailureInternal,
			code:     codes.Internal,
		},
		{
			name: "joined internal branch fails closed over domain branch",
			err: errors.Join(
				sharedErrors.NewMalformedRequestError("malformed"),
				ownedInternalError{},
			),
			category: commonsemantic.FailureInternal,
			code:     codes.Internal,
		},
		{
			name:     "opaque error fails closed",
			err:      errors.New("opaque"),
			category: commonsemantic.FailureInternal,
			code:     codes.Internal,
		},
		{
			name: "unreviewed domain condition fails closed",
			err: &sharedErrors.DomainWireError{
				Kind: sharedErrors.WireConditionKind("FutureCondition"),
			},
			category: commonsemantic.FailureInternal,
			code:     codes.Internal,
		},
		{
			name:     "request-too-large without reviewed maximum fails closed",
			err:      sharedErrors.NewRequestTooLargeError("too large"),
			category: commonsemantic.FailureInternal,
			code:     codes.Internal,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			classified := classifyPlatformEngineError(
				context.Background(), test.err, commonsemantic.InvalidComponentCommandData,
			)
			wireErr := mapping.ToPlatformStatusError(classified, filessemantic.CapabilityName)
			if got := status.Code(wireErr); got != test.code {
				t.Fatalf("gRPC code = %v, want %v", got, test.code)
			}
			var failure *commonsemantic.Failure
			decoded := mapping.FromPlatformStatusError(wireErr, filessemantic.CapabilityName)
			if !errors.As(decoded, &failure) || failure.Category != test.category {
				t.Fatalf("decoded failure = %#v, want category %q", decoded, test.category)
			}
		})
	}
}

func TestPlatformEngineClassifierAttributesCancellationOnlyToRequestContext(t *testing.T) {
	canceled, cancel := context.WithCancel(context.Background())
	cancel()
	deadline, cancelDeadline := context.WithDeadline(context.Background(), time.Now().Add(-time.Second))
	defer cancelDeadline()
	tests := []struct {
		name string
		ctx  context.Context
		code codes.Code
	}{
		{name: "caller canceled", ctx: canceled, code: codes.Canceled},
		{name: "caller deadline exceeded", ctx: deadline, code: codes.DeadlineExceeded},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			classified := classifyPlatformEngineError(
				test.ctx, errors.New("owner returned opaque error"),
				commonsemantic.InvalidComponentCommandData,
			)
			wireErr := mapping.ToPlatformStatusError(classified, filessemantic.CapabilityName)
			if got := status.Code(wireErr); got != test.code {
				t.Fatalf("gRPC code = %v, want %v", got, test.code)
			}
		})
	}
}

func TestRuntimeEngineClassifiersPreserveTypedRetriableCondition(t *testing.T) {
	err := permissions.ErrFieldPermissionProjectionOverloaded
	classifiers := map[string]func(error) error{
		"selected root": classifyEngineError,
		"record access": classifyRecordAccessEngineError,
		"record query":  classifyQueryEngineError,
	}
	for name, classify := range classifiers {
		t.Run(name, func(t *testing.T) {
			var failure *commonsemantic.Failure
			if !errors.As(classify(err), &failure) {
				t.Fatal("typed Runtime failure was not returned")
			}
			if failure.Category != commonsemantic.FailureUnavailable || failure.RetryAfterMillis != 1000 {
				t.Fatalf("failure = %#v, want unavailable with one-second retry", failure)
			}
		})
	}
}

func TestRuntimeEngineClassifierDoesNotInventRetryForInternalError(t *testing.T) {
	err := errors.New("ordinary internal error")
	failure, ok := classifyRetriableEngineCondition(err)
	if ok || failure != nil {
		t.Fatalf("ordinary error classified as retriable: %#v", failure)
	}
}

func TestRuntimeEngineClassifiersRespectOwningInfrastructureCondition(t *testing.T) {
	retriable := permissions.ErrFieldPermissionProjectionOverloaded
	tests := []struct {
		name       string
		err        error
		category   commonsemantic.FailureCategory
		retryAfter uint64
	}{
		{name: "transparent wrapper preserves retriable owner", err: fmt.Errorf("transparent: %w", retriable), category: commonsemantic.FailureUnavailable, retryAfter: 1000},
		{name: "outer internal owner wins", err: ownedInternalError{cause: retriable}, category: commonsemantic.FailureInternal},
		{name: "joined internal branch fails closed", err: errors.Join(retriable, ownedInternalError{}), category: commonsemantic.FailureInternal},
	}
	classifiers := map[string]func(error) error{
		"selected root": classifyEngineError,
		"record access": classifyRecordAccessEngineError,
		"record query":  classifyQueryEngineError,
	}
	for classifierName, classify := range classifiers {
		for _, test := range tests {
			t.Run(classifierName+"/"+test.name, func(t *testing.T) {
				var failure *commonsemantic.Failure
				if !errors.As(classify(test.err), &failure) {
					t.Fatal("typed Runtime failure was not returned")
				}
				if failure.Category != test.category || failure.RetryAfterMillis != test.retryAfter {
					t.Fatalf("failure = %#v, want category %v retry-after %d", failure, test.category, test.retryAfter)
				}
			})
		}
	}
}

type ownedInternalError struct{ cause error }

func (e ownedInternalError) Error() string           { return "owned internal error" }
func (e ownedInternalError) Unwrap() error           { return e.cause }
func (e ownedInternalError) InternalCondition() bool { return true }
