package runtimeapi

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

	"github.com/nexuscrm/persistence"
	"github.com/pingcap-inc/nexuscrm-api-host/processhttp"
	applicationresultsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/applicationresultv1"
	commandsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commandv1"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	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/credentials/insecure"
)

type applicationResultTestEngine struct {
	response applicationresultsemantic.GetResultResponse
	err      error
	cancel   context.CancelFunc
	calls    int
	scope    persistence.RuntimePlatformCommandTrustedScope
	request  applicationresultsemantic.GetResultRequest
}

func (e *applicationResultTestEngine) GetApplicationResult(
	ctx context.Context,
	scope persistence.RuntimePlatformCommandTrustedScope,
	request applicationresultsemantic.GetResultRequest,
) (applicationresultsemantic.GetResultResponse, error) {
	e.calls++
	e.scope = scope
	e.request = request
	if e.cancel != nil {
		e.cancel()
	}
	return e.response, e.err
}

func TestApplicationResultAdmissionUsesCurrentDurableGrantWithoutCatalog(t *testing.T) {
	now := time.Now().UTC()
	state := applicationResultSecurityState(now, 3)
	var selectedInstallation, selectedWorkload string
	admission, err := NewApplicationResultAdmission(
		ApplicationResultSelection{
			AppInstallationID: testInstallationID,
			WorkloadID:        testWorkloadID,
		},
		func(
			_ context.Context,
			appInstallationID string,
			callerWorkloadID string,
		) (persistence.RuntimeSecurityIssuanceState, error) {
			selectedInstallation = appInstallationID
			selectedWorkload = callerWorkloadID
			return state, nil
		},
	)
	if err != nil {
		t.Fatal(err)
	}
	ctx := context.WithValue(
		t.Context(),
		trustedWorkloadCallContextKey{},
		TrustedWorkloadCall{
			orgID: testOrgID, callerWorkloadID: testWorkloadID,
			capability:    applicationresultsemantic.CapabilityName,
			securityEpoch: 3,
		},
	)
	admitted, err := admission.bind(ctx)
	if err != nil {
		t.Fatal(err)
	}
	scope, ok := applicationResultScopeFromAdmission(admitted)
	if !ok || selectedInstallation != testInstallationID ||
		selectedWorkload != testWorkloadID ||
		scope.OrgID != testOrgID ||
		scope.AppInstallationID != testInstallationID ||
		scope.CallerWorkloadID != testWorkloadID ||
		scope.RuntimeCapabilityID != commandsemantic.CapabilityName {
		t.Fatalf(
			"application-result authority = scope %#v selected %q/%q",
			scope,
			selectedInstallation,
			selectedWorkload,
		)
	}
}

func TestApplicationResultAdmissionFailsClosedOnDurableAuthority(t *testing.T) {
	now := time.Now().UTC()
	base := applicationResultSecurityState(now, 3)
	tests := []struct {
		name     string
		mutate   func(*persistence.RuntimeSecurityIssuanceState)
		category commonsemantic.FailureCategory
	}{
		{
			name: "missing state",
			mutate: func(state *persistence.RuntimeSecurityIssuanceState) {
				state.StateFound = false
			},
			category: commonsemantic.FailureUnavailable,
		},
		{
			name: "missing binding",
			mutate: func(state *persistence.RuntimeSecurityIssuanceState) {
				state.BindingFound = false
			},
			category: commonsemantic.FailureForbidden,
		},
		{
			name: "removed capability",
			mutate: func(state *persistence.RuntimeSecurityIssuanceState) {
				state.Capabilities = nil
			},
			category: commonsemantic.FailureForbidden,
		},
		{
			name: "foreign org",
			mutate: func(state *persistence.RuntimeSecurityIssuanceState) {
				state.OrgID = "other-org"
			},
			category: commonsemantic.FailureForbidden,
		},
		{
			name: "security epoch changed",
			mutate: func(state *persistence.RuntimeSecurityIssuanceState) {
				state.SecurityEpoch = 4
				state.LeaseSecurityEpoch = 4
			},
			category: commonsemantic.FailureStaleState,
		},
		{
			name: "expired lease",
			mutate: func(state *persistence.RuntimeSecurityIssuanceState) {
				state.LeaseExpiresAt = state.AuthorityNow
			},
			category: commonsemantic.FailureStaleState,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			state := base
			state.Capabilities = append([]string(nil), base.Capabilities...)
			test.mutate(&state)
			admission, err := NewApplicationResultAdmission(
				ApplicationResultSelection{
					AppInstallationID: testInstallationID,
					WorkloadID:        testWorkloadID,
				},
				func(
					context.Context,
					string,
					string,
				) (persistence.RuntimeSecurityIssuanceState, error) {
					return state, nil
				},
			)
			if err != nil {
				t.Fatal(err)
			}
			ctx := context.WithValue(
				t.Context(),
				trustedWorkloadCallContextKey{},
				TrustedWorkloadCall{
					orgID: testOrgID, callerWorkloadID: testWorkloadID,
					capability:    applicationresultsemantic.CapabilityName,
					securityEpoch: 3,
				},
			)
			_, err = admission.bind(ctx)
			var failure *applicationresultsemantic.Failure
			if !errors.As(err, &failure) || failure.Category != test.category {
				t.Fatalf("admission error = %#v, want %s", err, test.category)
			}
		})
	}
}

func TestApplicationResultServerPrefersCallerCancellation(t *testing.T) {
	ctx, cancel := context.WithTimeout(t.Context(), time.Second)
	ctx = context.WithValue(
		ctx,
		applicationResultScopeContextKey{},
		persistence.RuntimePlatformCommandTrustedScope{
			OrgID: testOrgID, AppInstallationID: testInstallationID,
			CallerWorkloadID:    testWorkloadID,
			RuntimeCapabilityID: commandsemantic.CapabilityName,
		},
	)
	request := applicationResultRequest("command-cancel")
	wireRequest, err := mapping.ToWireGetApplicationResultRequest(ctx, request)
	if err != nil {
		t.Fatal(err)
	}
	engine := &applicationResultTestEngine{
		response: applicationresultsemantic.GetResultResponse{
			CommandID:          request.CommandID,
			ExecutionRefDigest: request.ExecutionRefDigest,
			State:              applicationresultsemantic.ResultPending,
		},
		cancel: cancel,
	}
	server, err := NewApplicationResultServer(engine)
	if err != nil {
		t.Fatal(err)
	}
	_, err = server.GetApplicationResult(ctx, wireRequest)
	decoded := mapping.FromPlatformStatusError(
		err,
		applicationresultsemantic.CapabilityName,
	)
	var failure *applicationresultsemantic.Failure
	if !errors.As(decoded, &failure) ||
		failure.Category != commonsemantic.FailureCanceled {
		t.Fatalf("canceled result read = %#v, decoded %#v", err, decoded)
	}
}

func TestRuntimeAPIProcessApplicationResultCrossesPrivateEndpoint(t *testing.T) {
	now := time.Now().UTC()
	request := applicationResultRequest("command-process")
	engine := &applicationResultTestEngine{
		response: applicationresultsemantic.GetResultResponse{
			CommandID:          request.CommandID,
			ExecutionRefDigest: request.ExecutionRefDigest,
			State:              applicationresultsemantic.ResultPending,
		},
	}
	admission, err := NewApplicationResultAdmission(
		ApplicationResultSelection{
			AppInstallationID: testInstallationID,
			WorkloadID:        testWorkloadID,
		},
		func(
			context.Context,
			string,
			string,
		) (persistence.RuntimeSecurityIssuanceState, error) {
			return applicationResultSecurityState(now, 1), nil
		},
	)
	if err != nil {
		t.Fatal(err)
	}
	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),
		ApplicationResultAdmission: admission,
		ApplicationResults:         engine,
		PlatformCapabilityMaxAdmissionLeases: map[string]time.Duration{
			applicationresultsemantic.CapabilityName: time.Second,
		},
	}, &readerStub{})
	if err != nil {
		t.Fatal(err)
	}
	if err := process.Start(); err != nil {
		t.Fatal(err)
	}
	defer stopProcess(t, process)
	connection, err := grpc.NewClient(
		"passthrough:///"+process.GRPCAddress(),
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	if err != nil {
		t.Fatal(err)
	}
	defer connection.Close()
	ctx, cancel := context.WithTimeout(
		platformWorkloadProtocolContext(
			context.Background(),
			applicationresultsemantic.CapabilityName,
		),
		time.Second,
	)
	defer cancel()
	wireRequest, err := mapping.ToWireGetApplicationResultRequest(ctx, request)
	if err != nil {
		t.Fatal(err)
	}
	wireResponse, err := runtimev1.NewApplicationResultServiceClient(
		connection,
	).GetApplicationResult(ctx, wireRequest)
	if err != nil {
		t.Fatal(err)
	}
	response, err := mapping.FromWireGetApplicationResultResponse(
		request,
		wireResponse,
	)
	if err != nil {
		t.Fatal(err)
	}
	if response.State != applicationresultsemantic.ResultPending ||
		engine.calls != 1 || engine.request != request ||
		engine.scope.RuntimeCapabilityID != commandsemantic.CapabilityName {
		t.Fatalf(
			"private result read = response %#v engine %#v",
			response,
			engine,
		)
	}
	capabilities := readRuntimeMetrics(
		t,
		"http://"+process.AdminAddress()+processhttp.CapabilitiesPath,
	)
	if !strings.Contains(
		capabilities,
		`"`+applicationresultsemantic.CapabilityName+`"`,
	) {
		t.Fatalf("Runtime capabilities = %s", capabilities)
	}
	assertRuntimeProbe(
		t,
		"http://"+process.AdminAddress()+processhttp.ReadyPath,
		http.StatusOK,
	)
}

func TestRuntimeAPIProcessApplicationResultRequiresExactEnablement(t *testing.T) {
	now := time.Now().UTC()
	admission, err := NewApplicationResultAdmission(
		ApplicationResultSelection{
			AppInstallationID: testInstallationID,
			WorkloadID:        testWorkloadID,
		},
		func(
			context.Context,
			string,
			string,
		) (persistence.RuntimeSecurityIssuanceState, error) {
			return applicationResultSecurityState(now, 1), nil
		},
	)
	if err != nil {
		t.Fatal(err)
	}
	base := 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),
	}
	leaseOnly := base
	leaseOnly.PlatformCapabilityMaxAdmissionLeases = map[string]time.Duration{
		applicationresultsemantic.CapabilityName: time.Second,
	}
	if _, err := Assemble(leaseOnly, &readerStub{}); err == nil ||
		!strings.Contains(err.Error(), "requires the durable result engine") {
		t.Fatalf("result lease without authority = %v", err)
	}
	authorityOnly := base
	authorityOnly.ApplicationResultAdmission = admission
	authorityOnly.ApplicationResults = &applicationResultTestEngine{}
	if _, err := Assemble(authorityOnly, &readerStub{}); err == nil ||
		!strings.Contains(err.Error(), "requires explicit admission enablement") {
		t.Fatalf("result authority without lease = %v", err)
	}
}

func applicationResultSecurityState(
	now time.Time,
	securityEpoch uint64,
) persistence.RuntimeSecurityIssuanceState {
	return persistence.RuntimeSecurityIssuanceState{
		StateFound: true, BindingFound: true, BindingActive: true,
		OrgID:         testOrgID,
		Capabilities:  []string{applicationresultsemantic.CapabilityName},
		SecurityEpoch: securityEpoch, LeaseFound: true, LeaseSecurityEpoch: securityEpoch,
		LeaseExpiresAt: now.Add(time.Minute), AuthorityNow: now,
	}
}

func applicationResultRequest(
	commandID string,
) applicationresultsemantic.GetResultRequest {
	return applicationresultsemantic.GetResultRequest{
		CommandID:          commandID,
		ExecutionRefDigest: "sha256:" + strings.Repeat("a", 64),
	}
}
