package runtimeapi

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

	"github.com/pingcap-inc/nexuscrm-api-host/runtimegrpc"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/authorizationv1"
	identitysemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/identityv1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/envelope"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/metadata"
)

type authorizationProcessEngine struct{ t *testing.T }

func (engine *authorizationProcessEngine) BeginAuthorization(ctx context.Context, authority RuntimeWorkloadCall, request semantic.BeginRequest) (semantic.BeginResponse, error) {
	engine.assertBoundary(ctx, authority, request.AppInstallationID)
	return semantic.BeginResponse{RequestHandle: "handle-1", ExpiresAtUnixSecond: time.Now().Add(time.Minute).Unix()}, nil
}
func (engine *authorizationProcessEngine) CompleteAuthorization(ctx context.Context, authority RuntimeWorkloadCall, credentials runtimegrpc.SessionPresentedCredentials, request semantic.CompleteRequest) (semantic.CompleteResponse, runtimegrpc.IssuedAuthorizationCredentials, error) {
	engine.assertBoundary(ctx, authority, request.AppInstallationID)
	if credentials.AccessToken != "browser-token" || request.RequestHandle != "handle-1" {
		engine.t.Fatalf("complete credentials=%#v request=%#v", credentials, request)
	}
	return semantic.CompleteResponse{AuthorizationCodeIssued: true, RedirectURI: "https://client.example/callback", State: "state-1"}, runtimegrpc.IssuedAuthorizationCredentials{AuthorizationCode: "code-1"}, nil
}
func (engine *authorizationProcessEngine) ExchangeAuthorizationToken(ctx context.Context, authority RuntimeWorkloadCall, credentials runtimegrpc.AuthorizationExchangeCredentials, request semantic.ExchangeRequest) (semantic.ExchangeResponse, runtimegrpc.IssuedAuthorizationCredentials, error) {
	engine.assertBoundary(ctx, authority, request.AppInstallationID)
	if request.GrantType == semantic.GrantTypeClientCredentials {
		if credentials.ClientSecret != "machine-secret" || credentials.AuthorizationCode != "" || credentials.CodeVerifier != "" || credentials.RefreshToken != "" {
			engine.t.Fatalf("client credentials=%#v", credentials)
		}
		return semantic.ExchangeResponse{Token: &semantic.Token{TokenType: "Bearer", ExpiresInSecond: 3600,
			AccessTokenIssued: true}}, runtimegrpc.IssuedAuthorizationCredentials{AccessToken: "machine-access-token"}, nil
	}
	if credentials.AuthorizationCode != "code-1" || credentials.CodeVerifier != "verifier" {
		engine.t.Fatalf("exchange credentials=%#v", credentials)
	}
	return semantic.ExchangeResponse{Token: &semantic.Token{TokenType: "Bearer", ExpiresInSecond: 3600,
			AccessTokenIssued: true, RefreshTokenIssued: true}},
		runtimegrpc.IssuedAuthorizationCredentials{AccessToken: "access-token", RefreshToken: "refresh-token"}, nil
}
func (engine *authorizationProcessEngine) assertBoundary(ctx context.Context, authority RuntimeWorkloadCall, installationID string) {
	engine.t.Helper()
	if authority.OrgID != testOrgID || authority.CallerWorkloadID != testWorkloadID || authority.Capability != semantic.CapabilityName ||
		authority.SecurityEpoch != 1 || installationID != testInstallationID {
		engine.t.Fatalf("authority=%#v installation=%q", authority, installationID)
	}
	if _, ok := runtimegrpc.RuntimeAuthorizationExchangeCredentials(ctx); ok {
		engine.t.Fatal("exchange credentials remained in engine context")
	}
	if _, ok := runtimegrpc.RuntimeSessionPresentedCredentials(ctx); ok {
		engine.t.Fatal("session credentials remained in engine context")
	}
	values, _ := metadata.FromIncomingContext(ctx)
	for _, key := range []string{runtimegrpc.AuthorizationCodeMetadataKey, runtimegrpc.AuthorizationCodeVerifierMetadataKey,
		runtimegrpc.AuthorizationRefreshTokenMetadataKey, runtimegrpc.AuthorizationClientSecretMetadataKey,
		runtimegrpc.EndUserAuthorizationMetadataKey, envelope.RuntimeDelegationMetadataKey} {
		if len(values.Get(key)) != 0 {
			engine.t.Fatalf("engine metadata retained %q", key)
		}
	}
}

func TestRuntimeAPIProcessServesAuthorizationLifecycleOnExistingEndpoint(t *testing.T) {
	process, err := Assemble(ProcessConfig{Version: "authorization-process-test", AdminAddress: "127.0.0.1:0", GRPCAddress: "127.0.0.1:0",
		AdmissionLimit: 2, ServingEnabled: true,
		WorkloadAuthority: func(context.Context, string) (runtimegrpc.VerifiedCaller, error) {
			return runtimegrpc.VerifiedCaller(testWorkloadID), nil
		},
		SecurityAdmission: testSecurityAdmission(t), Receipts: ReceiptSourceFunc(BootstrapReceipts),
		IdentityExchange: func(context.Context, identitysemantic.Request) (identitysemantic.Response, error) {
			return identitysemantic.Response{}, errors.New("not used")
		},
		IdentityAttemptCap: time.Second, SessionLifecycle: &sessionProcessEngine{t: t},
		AuthorizationLifecycle: &authorizationProcessEngine{t: t}}, &readerStub{})
	if err != nil {
		t.Fatal(err)
	}
	if err := process.Start(); err != nil {
		t.Fatal(err)
	}
	defer stopProcess(t, process)
	connection, err := grpc.NewClient("dns:///"+process.GRPCAddress(), grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		t.Fatal(err)
	}
	defer connection.Close()
	wireClient := runtimev1.NewAuthorizationLifecycleServiceClient(connection)
	client, err := runtimegrpc.NewAuthorizationLifecycleClient(wireClient.BeginAuthorization, wireClient.CompleteAuthorization, wireClient.ExchangeAuthorizationToken)
	if err != nil {
		t.Fatal(err)
	}
	base := metadata.AppendToOutgoingContext(context.Background(), "authorization", "Bearer workload-secret")
	ctx, cancel := context.WithTimeout(base, time.Second)
	defer cancel()
	begin, err := client.Begin(ctx, semantic.BeginRequest{AppInstallationID: testInstallationID, ClientID: "public-client",
		RedirectURI: "https://client.example/callback", ResponseType: "code", Scope: "api", CodeChallenge: "challenge",
		CodeChallengeMethod: "S256", Issuer: "https://crm.example"})
	if err != nil || begin.RequestHandle != "handle-1" {
		t.Fatalf("begin=%#v err=%v", begin, err)
	}
	complete, completed, err := client.Complete(ctx, runtimegrpc.SessionPresentedCredentials{AccessToken: "browser-token"},
		semantic.CompleteRequest{AppInstallationID: testInstallationID, RequestHandle: begin.RequestHandle})
	if err != nil || !complete.AuthorizationCodeIssued || completed.AuthorizationCode != "code-1" {
		t.Fatalf("complete=%#v issued=%#v err=%v", complete, completed, err)
	}
	exchange, issued, err := client.Exchange(ctx, runtimegrpc.AuthorizationExchangeCredentials{AuthorizationCode: completed.AuthorizationCode, CodeVerifier: "verifier"},
		semantic.ExchangeRequest{AppInstallationID: testInstallationID, GrantType: semantic.GrantTypeAuthorizationCode,
			ClientID: "public-client", RedirectURI: complete.RedirectURI, Issuer: "https://crm.example"})
	if err != nil || exchange.Token == nil || issued.AccessToken != "access-token" || issued.RefreshToken != "refresh-token" {
		t.Fatalf("exchange=%#v issued=%#v err=%v", exchange, issued, err)
	}
	machine, machineIssued, err := client.Exchange(ctx, runtimegrpc.AuthorizationExchangeCredentials{ClientSecret: "machine-secret"},
		semantic.ExchangeRequest{AppInstallationID: testInstallationID, GrantType: semantic.GrantTypeClientCredentials,
			ClientID: "confidential-client", RequestedScope: "api.read", Issuer: "https://crm.example"})
	if err != nil || machine.Token == nil || machine.Token.RefreshTokenIssued || machineIssued.AccessToken != "machine-access-token" || machineIssued.RefreshToken != "" {
		t.Fatalf("machine=%#v issued=%#v err=%v", machine, machineIssued, err)
	}
}
