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/sessionv1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

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

func TestSessionLifecycleStatusMappingPreservesClosedFailureGrammar(t *testing.T) {
	for _, source := range []*semantic.Failure{
		{Category: semantic.FailureUnauthenticated},
		{Category: semantic.FailureForbidden},
		{Category: semantic.FailureCanceled},
		{Category: semantic.FailureInvalidRequest, InvalidComponent: semantic.InvalidComponentOperation},
		{
			Category:  semantic.FailureResourceExhausted,
			LimitKind: semantic.LimitAppInstallationID, Maximum: semantic.MaxIdentifierBytes,
		},
		{
			Category: semantic.FailureStaleState, StaleAxis: semantic.StaleAxisSecurity,
			RequiredRevision: 8, ServedRevision: 7,
		},
	} {
		decoded := FromPlatformStatusError(
			ToPlatformStatusError(source, semantic.CapabilityName), semantic.CapabilityName,
		)
		var failure *semantic.Failure
		if !errors.As(decoded, &failure) || !reflect.DeepEqual(failure, source) {
			t.Fatalf("status round trip = %#v, want %#v", decoded, source)
		}
	}

	invalid := &semantic.Failure{
		Category:  semantic.FailureResourceExhausted,
		LimitKind: semantic.LimitAppInstallationID, Maximum: semantic.MaxIdentifierBytes - 1,
	}
	decoded := FromPlatformStatusError(
		ToPlatformStatusError(invalid, semantic.CapabilityName), semantic.CapabilityName,
	)
	var failure *semantic.Failure
	if !errors.As(decoded, &failure) || failure.Category != semantic.FailureInternal {
		t.Fatalf("invalid status mapping = %#v", decoded)
	}

	missing := ToPlatformStatusError(
		&semantic.Failure{Category: commonsemantic.FailureMissing}, semantic.CapabilityName,
	)
	if status.Code(missing) != codes.Internal {
		t.Fatalf("outbound missing status = %v, want internal", status.Code(missing))
	}
	decoded = FromPlatformStatusError(status.Error(codes.NotFound, "must not disclose"), semantic.CapabilityName)
	if !errors.As(decoded, &failure) || failure.Category != semantic.FailureInternal {
		t.Fatalf("inbound missing status = %#v, want internal", decoded)
	}
}

func TestSessionLifecycleMappingRoundTrip(t *testing.T) {
	ctx := sessionDeadlineContext(t)
	create := semantic.CreateRequest{
		AppInstallationID: "installation-1", Method: semantic.AuthenticationMethodPassword,
		ApplicationClientID: "client-1", RequestedScope: "api", SourceAddress: "192.0.2.1",
		UserAgent: "browser", IssueControlProof: true,
	}
	wireCreate, err := ToWireCreateSessionRequest(ctx, create)
	if err != nil {
		t.Fatal(err)
	}
	decodedCreate, err := FromWireCreateSessionRequest(ctx, wireCreate)
	if err != nil || !reflect.DeepEqual(decodedCreate, create) {
		t.Fatalf("create = %#v, %v", decodedCreate, err)
	}

	session := semantic.Session{Principal: semantic.Principal{
		PrincipalID: "user-1", DisplayName: "Ada", Email: "ada@example.test",
		ProfileID: "profile-1", RoleID: "role-1",
	}, ExpiresAtUnixSecond: 42}
	createResponse := semantic.CreateResponse{Session: session, AccessTokenIssued: true, ControlProofIssued: true}
	wireCreateResponse, err := ToWireCreateSessionResponse(create, createResponse)
	if err != nil {
		t.Fatal(err)
	}
	decodedCreateResponse, err := FromWireCreateSessionResponse(create, wireCreateResponse)
	if err != nil || !reflect.DeepEqual(decodedCreateResponse, createResponse) {
		t.Fatalf("create response = %#v, %v", decodedCreateResponse, err)
	}

	read := semantic.ReadRequest{AppInstallationID: "installation-1", AllowRenewal: true, UseControlProof: true}
	wireRead, err := ToWireReadSessionRequest(ctx, read)
	if err != nil {
		t.Fatal(err)
	}
	decodedRead, err := FromWireReadSessionRequest(ctx, wireRead)
	if err != nil || decodedRead != read {
		t.Fatalf("read = %#v, %v", decodedRead, err)
	}

	readResponse := semantic.ReadResponse{Session: session, ReplacementAccessTokenIssued: true, ControlProofExpiresAtUnixSecond: 42}
	wireReadResponse, err := ToWireReadSessionResponse(read, readResponse)
	if err != nil {
		t.Fatal(err)
	}
	decodedReadResponse, err := FromWireReadSessionResponse(read, wireReadResponse)
	if err != nil || !reflect.DeepEqual(decodedReadResponse, readResponse) {
		t.Fatalf("read response = %#v, %v", decodedReadResponse, err)
	}

	revoke := semantic.RevokeRequest{AppInstallationID: "installation-1", UseControlProof: true}
	wireRevoke, err := ToWireRevokeSessionRequest(ctx, revoke)
	if err != nil {
		t.Fatal(err)
	}
	decodedRevoke, err := FromWireRevokeSessionRequest(ctx, wireRevoke)
	if err != nil || decodedRevoke != revoke {
		t.Fatalf("revoke = %#v, %v", decodedRevoke, err)
	}
}

func TestSessionDocumentPolicyWireRoundTripAndMissingDecision(t *testing.T) {
	request := semantic.ReadRequest{AppInstallationID: "installation-1", DocumentPolicy: &semantic.DocumentPolicyRequest{RouteFamilyPrefix: "/extensions/data/", LinkRoutes: []semantic.DocumentLinkRoute{{Link: "identity", Method: "GET", Path: "/identity/:userId"}}}}
	wireRequest, err := ToWireReadSessionRequest(sessionDeadlineContext(t), request)
	if err != nil {
		t.Fatal(err)
	}
	decodedRequest, err := FromWireReadSessionRequest(sessionDeadlineContext(t), wireRequest)
	if err != nil || !reflect.DeepEqual(request, decodedRequest) {
		t.Fatalf("request: %+v %v", decodedRequest, err)
	}
	for _, policy := range []*semantic.DocumentPolicy{{}, {Restricted: true}, {Restricted: true, AllowedLinks: []string{"identity", "query"}, HourlyLimit: 123, DailyLimit: 456}} {
		response := semantic.ReadResponse{Session: semantic.Session{Principal: semantic.Principal{PrincipalID: "user-1", DisplayName: "Ada", ProfileID: "profile-1"}, ExpiresAtUnixSecond: 42}, DocumentPolicy: policy}
		wireResponse, err := ToWireReadSessionResponse(request, response)
		if err != nil {
			t.Fatal(err)
		}
		decoded, err := FromWireReadSessionResponse(request, wireResponse)
		if err != nil || !reflect.DeepEqual(response, decoded) {
			t.Fatalf("response: %+v %v", decoded, err)
		}
		wireResponse.DocumentPolicy = nil
		if _, err := FromWireReadSessionResponse(request, wireResponse); err == nil {
			t.Fatal("older Runtime missing requested decision accepted")
		}
	}
}

func TestSessionDocumentRouteLimitMatchesSemanticAndWireAdmission(t *testing.T) {
	ctx := sessionDeadlineContext(t)
	request := semantic.ReadRequest{AppInstallationID: "installation-1", DocumentPolicy: &semantic.DocumentPolicyRequest{RouteFamilyPrefix: "/extensions/data/", LinkRoutes: make([]semantic.DocumentLinkRoute, semantic.MaxDocumentLinkRoutes+1)}}
	_, toErr := ToWireReadSessionRequest(ctx, request)
	_, fromErr := FromWireReadSessionRequest(ctx, &runtimev1.ReadSessionRequest{AppInstallationId: request.AppInstallationID, DocumentPolicy: &runtimev1.SessionDocumentPolicyRequest{RouteFamilyPrefix: "/extensions/data/", LinkRoutes: make([]*runtimev1.SessionDocumentLinkRoute, semantic.MaxDocumentLinkRoutes+1)}})
	preflightErr := PreflightPlatformWirePayload(appendWireBytesField(nil, 4, repeatedEmptyWireMessages(2, semantic.MaxDocumentLinkRoutes+1)), &runtimev1.ReadSessionRequest{})
	for name, err := range map[string]error{"semantic": semantic.ValidateReadRequest(ctx, request), "encode": toErr, "decode": fromErr, "preflight": preflightErr} {
		t.Run(name, func(t *testing.T) {
			assertPlatformLimitFailure(t, err, semantic.LimitOperationCount, semantic.MaxDocumentLinkRoutes)
			statusErr := ToPlatformStatusError(err, semantic.CapabilityName)
			if status.Code(statusErr) != codes.ResourceExhausted {
				t.Fatalf("status = %v", statusErr)
			}
			assertPlatformLimitFailure(t, FromPlatformStatusError(statusErr, semantic.CapabilityName), semantic.LimitOperationCount, semantic.MaxDocumentLinkRoutes)
		})
	}
}
