package runtimeapi

import (
	"context"
	"errors"
	"net/http"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/nexuscrm/metadata/catalogsnapshot"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-api-host/processhttp"
	"github.com/pingcap-inc/nexuscrm-api-host/runtimegrpc"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/delegationv1"
	permissionauthoringsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionauthoringv1"
	mutationsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordmutationv1"
	recordsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordsv1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/envelope"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

func TestRuntimeAPIProcessRegistersPermissionAuthoringOnTheExistingEndpoint(t *testing.T) {
	engine := &permissionAuthoringEngineStub{}
	process, err := Assemble(ProcessConfig{
		Version:                             "test-sha",
		AdminAddress:                        "127.0.0.1:0",
		GRPCAddress:                         "127.0.0.1:0",
		AdmissionLimit:                      2,
		ServingEnabled:                      true,
		WorkloadAuthority:                   testWorkloadAuthority,
		SecurityAdmission:                   testSecurityAdmission(t),
		PermissionAuthoring:                 engine,
		PermissionAuthoringOutcomeRetention: time.Hour,
		PlatformCapabilityMaxAdmissionLeases: map[string]time.Duration{
			permissionauthoringsemantic.CapabilityName: time.Second,
		},
		PlatformResourceClassAdmissionLimits: map[runtimegrpc.ResourceClass]int{
			runtimegrpc.ResourceClassHeavyWork: 1,
		},
	}, &readerStub{})
	if err != nil {
		t.Fatal(err)
	}
	if err := process.Start(); err != nil {
		t.Fatal(err)
	}
	defer stopProcess(t, process)
	assertRuntimeProbe(t, "http://"+process.AdminAddress()+processhttp.ReadyPath, http.StatusOK)
	capabilities := readRuntimeMetrics(t, "http://"+process.AdminAddress()+processhttp.CapabilitiesPath)
	if !strings.Contains(capabilities, permissionauthoringsemantic.CapabilityName) {
		t.Fatalf("Runtime endpoint capabilities = %s", capabilities)
	}
}

func TestRuntimeAPIProcessPermissionAuthoringUsesReconciledServingAuthority(t *testing.T) {
	fixture := newServingSetFixture(t, 7, []servingCapabilitySetEntry{
		{
			appCapabilityID:     "io.nexuscrm.sfcrm.records.mutation.v1",
			runtimeCapabilityID: mutationsemantic.CapabilityName,
			profile:             catalogsnapshot.ProjectionRecordMutationV1,
		},
		{
			appCapabilityID:     "io.nexuscrm.sfcrm.permissions.authoring.v1",
			runtimeCapabilityID: permissionauthoringsemantic.CapabilityName,
			profile:             catalogsnapshot.ProjectionRecordMutationV1,
		},
	})
	if err := fixture.state.ReconcileAll(t.Context(), fixture.selections); err != nil {
		t.Fatalf("reconcile Permission Authoring serving set: %v", err)
	}
	engine := &permissionAuthoringEngineStub{}
	process, err := Assemble(ProcessConfig{
		Version: "test-sha", AdminAddress: "127.0.0.1:0", GRPCAddress: "127.0.0.1:0",
		AdmissionLimit: 2, ServingEnabled: true,
		WorkloadAuthority: testWorkloadAuthority, SecurityAdmission: testSecurityAdmission(t),
		ServingAdmission:    fixture.admission,
		PermissionAuthoring: engine, PermissionAuthoringOutcomeRetention: time.Hour,
		PlatformCapabilityMaxAdmissionLeases: map[string]time.Duration{
			permissionauthoringsemantic.CapabilityName: time.Second,
		},
		PlatformResourceClassAdmissionLimits: map[runtimegrpc.ResourceClass]int{
			runtimegrpc.ResourceClassHeavyWork: 1,
		},
	}, &readerStub{})
	if err != nil {
		t.Fatal(err)
	}
	if err := process.Start(); err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { stopProcess(t, process) })
	connection, err := grpc.NewClient(
		"passthrough:///"+process.GRPCAddress(),
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = connection.Close() })
	client := runtimev1.NewPermissionAuthoringServiceClient(connection)
	ctx, cancel := context.WithTimeout(permissionAuthoringProtocolContext(context.Background()), time.Second)
	t.Cleanup(cancel)

	snapshotRequest := permissionauthoringsemantic.SnapshotRequest{
		Target: permissionauthoringsemantic.Target{Kind: permissionauthoringsemantic.TargetProfile, ID: "profile-1"},
	}
	wireSnapshot, err := mapping.ToWireReadPermissionAuthoringSnapshotRequest(ctx, snapshotRequest)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := client.ReadPermissionAuthoringSnapshot(ctx, wireSnapshot); err != nil {
		t.Fatalf("ReadPermissionAuthoringSnapshot through production interceptor chain: %v", err)
	}
	saveRequest := permissionAuthoringServerSaveRequest(t)
	wireSave, err := mapping.ToWireSavePermissionAuthoringRequest(ctx, saveRequest)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := client.SavePermissionAuthoring(ctx, wireSave); err != nil {
		t.Fatalf("SavePermissionAuthoring through production interceptor chain: %v", err)
	}
	statusRequest, err := permissionauthoringsemantic.StatusRequestFor(saveRequest)
	if err != nil {
		t.Fatal(err)
	}
	wireStatus, err := mapping.ToWireGetPermissionAuthoringStatusRequest(ctx, statusRequest)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := client.GetPermissionAuthoringStatus(ctx, wireStatus); err != nil {
		t.Fatalf("GetPermissionAuthoringStatus through production interceptor chain: %v", err)
	}
	if engine.snapshotCalls != 1 || engine.saveCalls != 1 || engine.statusCalls != 1 ||
		engine.contextErr != nil ||
		engine.admittedCapability != permissionauthoringsemantic.CapabilityName ||
		engine.mutationCapability != mutationsemantic.CapabilityName ||
		engine.invocation.Actor == nil || engine.invocation.Actor.ID != testPrincipalID ||
		engine.invocation.AuthorityScope != permissionAuthoringAuthorityScope(testOrgID, testInstallationID) {
		t.Fatalf(
			"production authoring authority calls=%d/%d/%d admitted=%q mutation=%q invocation=%+v contextErr=%v",
			engine.snapshotCalls, engine.saveCalls, engine.statusCalls,
			engine.admittedCapability, engine.mutationCapability, engine.invocation, engine.contextErr,
		)
	}
}

func TestPermissionAuthoringServerUsesOnlyTrustedRuntimeAuthority(t *testing.T) {
	engine := &permissionAuthoringEngineStub{}
	server, err := NewPermissionAuthoringServer(engine)
	if err != nil {
		t.Fatal(err)
	}
	ctx := permissionAuthoringServerTestContext(t)
	snapshotRequest := permissionauthoringsemantic.SnapshotRequest{
		Target: permissionauthoringsemantic.Target{Kind: permissionauthoringsemantic.TargetProfile, ID: "profile-1"},
	}
	wireSnapshot, err := mapping.ToWireReadPermissionAuthoringSnapshotRequest(ctx, snapshotRequest)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := server.ReadPermissionAuthoringSnapshot(ctx, wireSnapshot); err != nil {
		t.Fatalf("ReadPermissionAuthoringSnapshot(): %v", err)
	}
	if engine.snapshotCalls != 1 || engine.invocation.Actor == nil || engine.invocation.Actor.ID != "user-1" ||
		engine.invocation.AuthorityScope != permissionAuthoringAuthorityScope(testOrgID, testInstallationID) ||
		engine.invocation.AdmittedPermissionStateFloor.Revision() != 19 {
		t.Fatalf("trusted invocation = %+v calls=%d", engine.invocation, engine.snapshotCalls)
	}

	saveRequest := permissionAuthoringServerSaveRequest(t)
	wireSave, err := mapping.ToWireSavePermissionAuthoringRequest(ctx, saveRequest)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := server.SavePermissionAuthoring(ctx, wireSave); err != nil {
		t.Fatalf("SavePermissionAuthoring(): %v", err)
	}
	statusRequest, err := permissionauthoringsemantic.StatusRequestFor(saveRequest)
	if err != nil {
		t.Fatal(err)
	}
	wireStatus, err := mapping.ToWireGetPermissionAuthoringStatusRequest(ctx, statusRequest)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := server.GetPermissionAuthoringStatus(ctx, wireStatus); err != nil {
		t.Fatalf("GetPermissionAuthoringStatus(): %v", err)
	}
	if engine.saveCalls != 1 || engine.statusCalls != 1 {
		t.Fatalf("save/status calls = %d/%d", engine.saveCalls, engine.statusCalls)
	}
}

func TestPermissionAuthoringServerRejectsUnboundOrMismatchedAuthority(t *testing.T) {
	engine := &permissionAuthoringEngineStub{}
	server, err := NewPermissionAuthoringServer(engine)
	if err != nil {
		t.Fatal(err)
	}
	request := permissionauthoringsemantic.SnapshotRequest{
		Target: permissionauthoringsemantic.Target{Kind: permissionauthoringsemantic.TargetProfile, ID: "profile-1"},
	}
	mappingContext, cancel := context.WithTimeout(t.Context(), time.Second)
	t.Cleanup(cancel)
	wireRequest, err := mapping.ToWireReadPermissionAuthoringSnapshotRequest(mappingContext, request)
	if err != nil {
		t.Fatal(err)
	}
	for name, ctx := range map[string]context.Context{
		"unbound": mappingContext,
		"mismatched installation": func() context.Context {
			bound := permissionAuthoringServerTestContext(t)
			scope, _ := runtimeExecutionScopeFromContext(bound)
			scope.appInstallationID = "other-installation"
			return context.WithValue(bound, runtimeExecutionScopeContextKey{}, scope)
		}(),
	} {
		t.Run(name, func(t *testing.T) {
			_, callErr := server.ReadPermissionAuthoringSnapshot(ctx, wireRequest)
			if status.Code(callErr) != codes.Unavailable {
				t.Fatalf("code = %s, want Unavailable (%v)", status.Code(callErr), callErr)
			}
		})
	}
	if engine.snapshotCalls != 0 {
		t.Fatalf("untrusted requests reached engine %d times", engine.snapshotCalls)
	}
}

type permissionAuthoringEngineStub struct {
	invocation         permissions.PermissionAuthoringInvocation
	snapshotCalls      int
	saveCalls          int
	statusCalls        int
	admittedCapability string
	mutationCapability string
	contextErr         error
}

func (engine *permissionAuthoringEngineStub) observeRuntimeContext(ctx context.Context) {
	admitted, ok := RuntimeMetadataViewFromContext(ctx)
	if !ok {
		engine.contextErr = errors.New("admitted Runtime metadata view is missing")
		return
	}
	admittedBinding, err := catalogsnapshot.RuntimeWorkBindingFromView(admitted)
	if err != nil {
		engine.contextErr = err
		return
	}
	mutation, ok := RuntimeMutationMetadataViewFromContext(ctx)
	if !ok {
		engine.contextErr = errors.New("record-mutation sibling view is missing")
		return
	}
	mutationBinding, err := catalogsnapshot.RuntimeWorkBindingFromView(mutation)
	if err != nil {
		engine.contextErr = err
		return
	}
	engine.admittedCapability = admittedBinding.RuntimeCapabilityID
	engine.mutationCapability = mutationBinding.RuntimeCapabilityID
}

func (engine *permissionAuthoringEngineStub) ReadPermissionAuthoringSnapshot(
	ctx context.Context,
	invocation permissions.PermissionAuthoringInvocation,
	request permissionauthoringsemantic.SnapshotRequest,
) (permissionauthoringsemantic.SnapshotResponse, error) {
	engine.observeRuntimeContext(ctx)
	engine.invocation = invocation
	engine.snapshotCalls++
	token, err := permissionauthoringsemantic.NewAuthoringToken("permission-set-1", 3)
	if err != nil {
		return permissionauthoringsemantic.SnapshotResponse{}, err
	}
	return permissionauthoringsemantic.SnapshotResponse{
		CanonicalPermissionSetID: "permission-set-1",
		AuthoringToken:           token,
		Receipts:                 permissionAuthoringServerReceipts(),
	}, nil
}

func (engine *permissionAuthoringEngineStub) SavePermissionAuthoring(
	ctx context.Context,
	invocation permissions.PermissionAuthoringInvocation,
	request permissionauthoringsemantic.Request,
) (permissionauthoringsemantic.Response, error) {
	engine.observeRuntimeContext(ctx)
	engine.invocation = invocation
	engine.saveCalls++
	digest, err := permissionauthoringsemantic.CommandDigest(request)
	if err != nil {
		return permissionauthoringsemantic.Response{}, err
	}
	token, err := permissionauthoringsemantic.NewAuthoringToken("permission-set-1", 4)
	if err != nil {
		return permissionauthoringsemantic.Response{}, err
	}
	return permissionauthoringsemantic.Response{
		CommandID:                request.CommandID,
		CommandDigest:            digest,
		Outcome:                  permissionauthoringsemantic.OutcomeApplied,
		CanonicalPermissionSetID: "permission-set-1",
		AuthoringToken:           token,
		OutcomeExpiresAtUTC:      time.Now().UTC().Add(time.Hour).Format(time.RFC3339Nano),
		Receipts:                 permissionAuthoringServerReceipts(),
	}, nil
}

func (engine *permissionAuthoringEngineStub) GetPermissionAuthoringStatus(
	ctx context.Context,
	invocation permissions.PermissionAuthoringInvocation,
	request permissionauthoringsemantic.StatusRequest,
) (permissionauthoringsemantic.StatusResponse, error) {
	engine.observeRuntimeContext(ctx)
	engine.invocation = invocation
	engine.statusCalls++
	return permissionauthoringsemantic.StatusResponse{
		CommandID:     request.CommandID,
		CommandDigest: request.CommandDigest,
		State:         permissionauthoringsemantic.CommandAccepted,
	}, nil
}

func permissionAuthoringServerSaveRequest(t *testing.T) permissionauthoringsemantic.Request {
	t.Helper()
	token, err := permissionauthoringsemantic.NewAuthoringToken("permission-set-1", 3)
	if err != nil {
		t.Fatal(err)
	}
	return permissionauthoringsemantic.Request{
		CommandID:              "permission-save:1",
		Target:                 permissionauthoringsemantic.Target{Kind: permissionauthoringsemantic.TargetProfile, ID: "profile-1"},
		ExpectedAuthoringToken: token,
		Changes: permissionauthoringsemantic.Changes{
			System: []permissionauthoringsemantic.CapabilityGrantChange{{CapabilityID: "ManageUsers", Enabled: true}},
		},
	}
}

func permissionAuthoringServerTestContext(t *testing.T) context.Context {
	t.Helper()
	base, cancel := context.WithTimeout(context.Background(), time.Second)
	t.Cleanup(cancel)
	trusted := newTrustedCall(delegationv1.Claims{
		DelegationID:      "delegation-1",
		OrgID:             testOrgID,
		PrincipalID:       "user-1",
		SessionID:         "session-1",
		CallerWorkloadID:  testWorkloadID,
		AppInstallationID: testInstallationID,
		RuntimeAudience:   "runtime-test",
		Capabilities:      []string{permissionauthoringsemantic.CapabilityName},
		AuthEpoch:         2,
		SecurityEpoch:     3,
	}, models.UserSession{ID: "user-1", ProfileID: "profile-admin", ProfileName: "Administrator"})
	trusted.permissionFloor = effectivePermissionVersion(t, 19)
	ctx := context.WithValue(base, trustedCallContextKey{}, trusted)
	return context.WithValue(ctx, runtimeExecutionScopeContextKey{}, RuntimeExecutionScope{
		view:              queryResponseTestView(t),
		orgID:             testOrgID,
		appInstallationID: testInstallationID,
		workloadID:        testWorkloadID,
		capability:        permissionauthoringsemantic.CapabilityName,
		receipts:          permissionAuthoringServerReceipts(),
	})
}

func permissionAuthoringServerReceipts() recordsemantic.Receipts {
	return recordsemantic.Receipts{
		ProtocolVersion:   "1.0",
		CapabilityVersion: permissionauthoringsemantic.CapabilityName,
		CatalogRevision:   7,
		SchemaGeneration:  8,
		EngineGeneration:  9,
		SecurityEpoch:     3,
	}
}

func permissionAuthoringProtocolContext(ctx context.Context) context.Context {
	ctx = metadata.AppendToOutgoingContext(
		ctx,
		envelope.ProtocolMajorMetadataKey,
		strconv.FormatUint(uint64(permissionauthoringsemantic.ProtocolMajor), 10),
		envelope.ProtocolMinorMetadataKey,
		strconv.FormatUint(uint64(permissionauthoringsemantic.ProtocolMinor), 10),
		envelope.CapabilityMetadataKey,
		permissionauthoringsemantic.CapabilityName,
	)
	return withTestDelegation(
		ctx,
		mustSignTestDelegation(testClaimsFor(time.Now(), permissionauthoringsemantic.CapabilityName)),
	)
}
