package mapping

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

	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/federatedloginv1"
	identitysemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/identityv1"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/proto"
)

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

func TestFederatedLoginWireRoundTripPreservesOnlyTheNeutralTransaction(t *testing.T) {
	ctx := federatedLoginMappingContext(t)
	state := "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8"
	begin := semantic.BeginRequest{
		AppInstallationID: "installation-1",
		RedirectURI:       "https://app.example.test/oauth/callback",
	}
	wireBegin, err := ToWireBeginFederatedLoginRequest(ctx, begin)
	if err != nil {
		t.Fatal(err)
	}
	gotBegin, err := FromWireBeginFederatedLoginRequest(ctx, wireBegin)
	if err != nil || gotBegin != begin {
		t.Fatalf("begin round trip = %#v, %v", gotBegin, err)
	}

	beginResponse := semantic.BeginResponse{
		State: state, ExpiresAtUnixSecond: time.Now().Add(time.Minute).Unix(),
	}
	wireBeginResponse, err := ToWireBeginFederatedLoginResponse(beginResponse)
	if err != nil {
		t.Fatal(err)
	}
	gotBeginResponse, err := FromWireBeginFederatedLoginResponse(wireBeginResponse)
	if err != nil || gotBeginResponse != beginResponse {
		t.Fatalf("begin response round trip = %#v, %v", gotBeginResponse, err)
	}

	consume := semantic.ConsumeRequest{
		AppInstallationID: begin.AppInstallationID,
		State:             beginResponse.State,
		RedirectURI:       begin.RedirectURI,
	}
	wireConsume, err := ToWireConsumeFederatedLoginRequest(ctx, consume)
	if err != nil {
		t.Fatal(err)
	}
	gotConsume, err := FromWireConsumeFederatedLoginRequest(ctx, wireConsume)
	if err != nil || gotConsume != consume {
		t.Fatalf("consume round trip = %#v, %v", gotConsume, err)
	}
}

func TestFederatedLoginNilWireRequestsRemainTypedInvalidRequests(t *testing.T) {
	ctx := federatedLoginMappingContext(t)
	tests := []struct {
		name   string
		decode func() error
	}{
		{
			name: "begin",
			decode: func() error {
				_, err := FromWireBeginFederatedLoginRequest(ctx, nil)
				return err
			},
		},
		{
			name: "consume",
			decode: func() error {
				_, err := FromWireConsumeFederatedLoginRequest(ctx, nil)
				return err
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			decodeErr := test.decode()
			var failure *semantic.Failure
			if !errors.As(decodeErr, &failure) || failure.Category != semantic.FailureInvalidRequest ||
				failure.InvalidComponent != commonsemantic.InvalidComponentResponse {
				t.Fatalf("decode failure = %#v, %v", failure, decodeErr)
			}

			wireErr := ToFederatedLoginRequestStatusError(decodeErr)
			if got := status.Code(wireErr); got != codes.InvalidArgument {
				t.Fatalf("status code = %s, want %s", got, codes.InvalidArgument)
			}
			var roundFailure *semantic.Failure
			roundErr := FromFederatedLoginRequestStatusError(wireErr)
			if !errors.As(roundErr, &roundFailure) || roundFailure.Category != semantic.FailureInvalidRequest ||
				roundFailure.InvalidComponent != commonsemantic.InvalidComponentResponse {
				t.Fatalf("round-trip failure = %#v, %v", roundFailure, roundErr)
			}
		})
	}
}

func TestFederatedLoginConsumeStatusProjectionMakesStateOutcomesByteIdentical(t *testing.T) {
	tests := []struct {
		name   string
		source error
	}{
		{
			name: "unknown",
			source: &semantic.Failure{
				Category: semantic.FailureMissing,
				Receipts: &commonsemantic.Receipts{CapabilityVersion: semantic.CapabilityName},
			},
		},
		{
			name: "expired",
			source: &semantic.Failure{
				Category: semantic.FailureStaleState, StaleAxis: semantic.StaleAxisSecurity,
				RequiredRevision: 9, ServedRevision: 8,
			},
		},
		{name: "already used", source: &semantic.Failure{Category: semantic.FailureUnauthenticated}},
		{
			name: "redirect mismatch",
			source: &semantic.Failure{
				Category: semantic.FailureInvalidRequest, InvalidComponent: semantic.InvalidComponentFieldValue,
			},
		},
		{name: "authorization-shaped mismatch", source: &semantic.Failure{Category: semantic.FailureForbidden}},
	}

	var baseline []byte
	var baselineName string
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			wireErr := ToFederatedLoginConsumeStatusError(test.source)
			if got := status.Code(wireErr); got != codes.Unauthenticated {
				t.Fatalf("status code = %s, want %s", got, codes.Unauthenticated)
			}
			encoded, err := proto.MarshalOptions{Deterministic: true}.Marshal(status.Convert(wireErr).Proto())
			if err != nil {
				t.Fatal(err)
			}
			if baseline == nil {
				baseline = append([]byte(nil), encoded...)
				baselineName = test.name
			} else if !bytes.Equal(encoded, baseline) {
				t.Fatalf("wire status differs from %s:\n got %x\nwant %x", baselineName, encoded, baseline)
			}

			var failure *semantic.Failure
			decoded := FromFederatedLoginConsumeStatusError(wireErr)
			want := &semantic.Failure{Category: semantic.FailureUnauthenticated}
			if !errors.As(decoded, &failure) || !reflect.DeepEqual(failure, want) {
				t.Fatalf("decoded failure = %#v, %v; want %#v", failure, decoded, want)
			}
		})
	}
}

func TestFederatedLoginStatusProjectionSeparatesRequestAndOwnerFailures(t *testing.T) {
	invalidRequest := &semantic.Failure{
		Category: semantic.FailureInvalidRequest, InvalidComponent: semantic.InvalidComponentFieldValue,
	}
	if got := status.Code(ToFederatedLoginRequestStatusError(invalidRequest)); got != codes.InvalidArgument {
		t.Fatalf("request status = %s, want %s", got, codes.InvalidArgument)
	}
	if got := status.Code(ToFederatedLoginBeginStatusError(invalidRequest)); got != codes.Internal {
		t.Fatalf("begin owner status = %s, want %s", got, codes.Internal)
	}
	if got := status.Code(ToFederatedLoginConsumeStatusError(invalidRequest)); got != codes.Unauthenticated {
		t.Fatalf("consume owner status = %s, want %s", got, codes.Unauthenticated)
	}

	for _, code := range []codes.Code{codes.InvalidArgument, codes.Unauthenticated, codes.PermissionDenied, codes.NotFound} {
		var failure *semantic.Failure
		decoded := FromFederatedLoginConsumeStatusError(status.Error(code, "legacy state outcome"))
		if !errors.As(decoded, &failure) || !reflect.DeepEqual(failure, &semantic.Failure{Category: semantic.FailureUnauthenticated}) {
			t.Fatalf("legacy %s failure = %#v, %v", code, failure, decoded)
		}
	}
}

func TestFederatedLoginOwnerStatusRejectsForeignCompatibilityDetail(t *testing.T) {
	foreign := ToIdentityStatusError(&identitysemantic.Failure{Category: identitysemantic.FailureIncompatible})
	if got := status.Code(foreign); got != codes.FailedPrecondition {
		t.Fatalf("foreign source status = %s, want %s", got, codes.FailedPrecondition)
	}
	for name, decode := range map[string]func(error) error{
		"begin":   FromFederatedLoginBeginStatusError,
		"consume": FromFederatedLoginConsumeStatusError,
	} {
		t.Run(name, func(t *testing.T) {
			var failure *semantic.Failure
			decoded := decode(foreign)
			if !errors.As(decoded, &failure) || !reflect.DeepEqual(failure, &semantic.Failure{Category: semantic.FailureInternal}) {
				t.Fatalf("decoded foreign compatibility = %#v, %v", failure, decoded)
			}
		})
	}
}

func TestFederatedLoginStatusProjectionPreservesReviewedOperationalFailures(t *testing.T) {
	tests := []struct {
		name       string
		toStatus   func(error) error
		fromStatus func(error) error
		source     *semantic.Failure
		wantCode   codes.Code
		want       *semantic.Failure
	}{
		{
			name:       "begin local compatibility",
			toStatus:   ToFederatedLoginBeginStatusError,
			fromStatus: FromFederatedLoginBeginStatusError,
			source:     &semantic.Failure{Category: semantic.FailureIncompatible},
			wantCode:   codes.FailedPrecondition,
			want: &semantic.Failure{
				Category: semantic.FailureIncompatible, RequiredCapability: semantic.CapabilityName,
				SupportedMajor: semantic.ProtocolMajor, SupportedMinorMin: semantic.ProtocolMinor, SupportedMinorMax: semantic.ProtocolMinor,
			},
		},
		{
			name:       "begin bare admission",
			toStatus:   ToFederatedLoginBeginStatusError,
			fromStatus: FromFederatedLoginBeginStatusError,
			source:     &semantic.Failure{Category: semantic.FailureResourceExhausted},
			wantCode:   codes.ResourceExhausted,
			want:       &semantic.Failure{Category: semantic.FailureResourceExhausted},
		},
		{
			name:       "consume bare admission",
			toStatus:   ToFederatedLoginConsumeStatusError,
			fromStatus: FromFederatedLoginConsumeStatusError,
			source:     &semantic.Failure{Category: semantic.FailureResourceExhausted},
			wantCode:   codes.ResourceExhausted,
			want:       &semantic.Failure{Category: semantic.FailureResourceExhausted},
		},
		{
			name:     "begin security refresh",
			toStatus: ToFederatedLoginBeginStatusError, fromStatus: FromFederatedLoginBeginStatusError,
			source: &semantic.Failure{
				Category: semantic.FailureStaleState, StaleAxis: semantic.StaleAxisSecurity,
				RequiredRevision: 9, ServedRevision: 8,
			},
			wantCode: codes.FailedPrecondition,
			want: &semantic.Failure{
				Category: semantic.FailureStaleState, StaleAxis: semantic.StaleAxisSecurity,
				RequiredRevision: 9, ServedRevision: 8,
			},
		},
		{
			name:     "consume admission limit",
			toStatus: ToFederatedLoginConsumeStatusError, fromStatus: FromFederatedLoginConsumeStatusError,
			source:   &semantic.Failure{Category: semantic.FailureResourceExhausted, LimitKind: semantic.LimitAdmission, Maximum: 12},
			wantCode: codes.ResourceExhausted,
			want:     &semantic.Failure{Category: semantic.FailureResourceExhausted, LimitKind: semantic.LimitAdmission, Maximum: 12},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			wireErr := test.toStatus(test.source)
			if got := status.Code(wireErr); got != test.wantCode {
				t.Fatalf("status code = %s, want %s", got, test.wantCode)
			}
			var failure *semantic.Failure
			decoded := test.fromStatus(wireErr)
			if !errors.As(decoded, &failure) || !reflect.DeepEqual(failure, test.want) {
				t.Fatalf("decoded failure = %#v, %v; want %#v", failure, decoded, test.want)
			}
		})
	}
}
