package sfcrmappservice

import (
	"context"
	"encoding/json"
	"errors"
	"net"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/pingcap-inc/nexuscrm-api-host/runtimegrpc"
	catalogsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	permissionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
	flowsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/flowv1"
	identitysemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/identityv1"
	catalogv1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/catalog/v1"
	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"
)

type standardActionGRPCServer struct {
	runtimev1.UnimplementedIdentityContextServiceServer
	runtimev1.UnimplementedEffectivePermissionServiceServer
	runtimev1.UnimplementedFlowExecutionServiceServer
	catalogv1.UnimplementedConsumerViewServiceServer
	catalogv1.UnimplementedAppMetadataViewV3ServiceServer

	view        catalogsemantic.AppMetadataView
	permissions permissionsemantic.Response

	mu              sync.Mutex
	identityCalls   int
	catalogCalls    int
	permissionCalls int
	executeCalls    int
	previewCalls    int
	executeRequest  flowsemantic.CatalogActionRequest
	executeResponse *flowsemantic.ResponseV2
	previewRequest  flowsemantic.CatalogActionPreviewRequest
}

func (server *standardActionGRPCServer) ExecuteFlow(
	ctx context.Context,
	request *runtimev1.ExecuteFlowRequest,
) (*runtimev1.ExecuteFlowResponse, error) {
	if err := requireRecordFilesMetadata(ctx, false, true); err != nil {
		return nil, err
	}
	semanticRequest, err := mapping.FromWireExecuteCatalogActionRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, flowsemantic.CapabilityName)
	}
	server.mu.Lock()
	server.executeCalls++
	server.executeRequest = semanticRequest
	executeResponse := server.executeResponse
	server.mu.Unlock()
	if executeResponse != nil {
		return mapping.ToWireExecuteFlowResponseV2(*executeResponse)
	}
	return mapping.ToWireExecuteFlowResponseV2(flowsemantic.ResponseV2{
		ExecutionID:      "grpc-standard-action-execution",
		CanonicalOutputs: []byte(`{"contactId":"003grpc001"}`),
		Receipts:         standardActionGRPCReceipts(),
	})
}

func (server *standardActionGRPCServer) Exchange(
	ctx context.Context,
	request *runtimev1.ExchangeRequest,
) (*runtimev1.ExchangeResponse, error) {
	if err := requireRecordFilesMetadata(ctx, true, false); err != nil {
		return nil, err
	}
	semanticRequest, err := mapping.FromWireIdentityExchangeRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToIdentityStatusError(err)
	}
	flowClosure := len(semanticRequest.RequestedCapabilities) == 2 &&
		semanticRequest.RequestedCapabilities[0] == catalogsemantic.AppMetadataV3CapabilityName &&
		semanticRequest.RequestedCapabilities[1] == flowsemantic.CapabilityName
	singleCapability := len(semanticRequest.RequestedCapabilities) == 1 &&
		semanticRequest.RequestedCapabilities[0] == permissionsemantic.CapabilityName
	if !flowClosure && !singleCapability {
		return nil, errors.New("unexpected delegated Standard Action capability")
	}
	server.mu.Lock()
	server.identityCalls++
	server.mu.Unlock()
	return mapping.ToWireIdentityExchangeResponse(identitysemantic.Response{
		OpaqueDelegation:    "header.payload.signature",
		ExpiresAtUnixSecond: time.Now().Add(time.Minute).Unix(),
	})
}

func (server *standardActionGRPCServer) GetAppMetadataView(
	ctx context.Context,
	request *catalogv1.GetAppMetadataViewRequest,
) (*catalogv1.GetAppMetadataViewResponse, error) {
	if err := requireRecordFilesMetadata(ctx, false, false); err != nil {
		return nil, err
	}
	semanticRequest, err := mapping.FromWireCatalogGetRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, catalogsemantic.AppMetadataCapabilityName)
	}
	if semanticRequest.MinCatalogRevision != nil &&
		*semanticRequest.MinCatalogRevision > server.view.Receipts.CatalogRevision {
		return nil, errors.New("stale Standard Action Catalog authority")
	}
	server.mu.Lock()
	server.catalogCalls++
	server.mu.Unlock()
	return mapping.ToWireGetAppMetadataViewResponse(server.view)
}

func (server *standardActionGRPCServer) GetAppMetadataViewV3(
	ctx context.Context,
	request *catalogv1.GetAppMetadataViewV3Request,
) (*catalogv1.GetAppMetadataViewV3Response, error) {
	if err := requireRecordFilesMetadata(ctx, false, false); err != nil {
		return nil, err
	}
	semanticRequest, err := mapping.FromWireCatalogGetV3Request(ctx, request)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(
			err, catalogsemantic.AppMetadataV3CapabilityName,
		)
	}
	if semanticRequest.MinCatalogRevision != nil &&
		*semanticRequest.MinCatalogRevision > server.view.Receipts.CatalogRevision {
		return nil, errors.New("stale Standard Action Catalog authority")
	}
	server.mu.Lock()
	server.catalogCalls++
	server.mu.Unlock()
	view := standardActionsV3View(server.view)
	return mapping.ToWireGetAppMetadataViewV3Response(view)
}

func (server *standardActionGRPCServer) ReadEffectivePermissions(
	ctx context.Context,
	request *runtimev1.ReadEffectivePermissionsRequest,
) (*runtimev1.ReadEffectivePermissionsResponse, error) {
	if err := requireRecordFilesMetadata(ctx, false, true); err != nil {
		return nil, err
	}
	semanticRequest, err := mapping.FromWireReadEffectivePermissionsRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, permissionsemantic.CapabilityName)
	}
	if semanticRequest.MinCatalogRevision == nil ||
		*semanticRequest.MinCatalogRevision != server.view.Receipts.CatalogRevision {
		return nil, errors.New("Standard Action permission read lost exact Catalog revision")
	}
	server.mu.Lock()
	server.permissionCalls++
	server.mu.Unlock()
	return mapping.ToWireReadEffectivePermissionsResponse(
		selectedPermissionTestResponse(server.permissions, semanticRequest),
	)
}

func (server *standardActionGRPCServer) PreviewCatalogAction(
	ctx context.Context,
	request *runtimev1.PreviewCatalogActionRequest,
) (*runtimev1.PreviewCatalogActionResponse, error) {
	if err := requireRecordFilesMetadata(ctx, false, true); err != nil {
		return nil, err
	}
	semanticRequest, err := mapping.FromWirePreviewCatalogActionRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, flowsemantic.CapabilityName)
	}
	server.mu.Lock()
	server.previewCalls++
	server.previewRequest = semanticRequest
	server.mu.Unlock()
	return mapping.ToWirePreviewCatalogActionResponse(
		semanticRequest,
		flowsemantic.CatalogActionPreviewResponse{
			CanonicalResult: []byte(`{"isSuccess":true,"outputValues":{"campaignFound":true}}`),
			Receipts:        standardActionGRPCReceipts(),
		},
	)
}

func TestStandardActionsUseOneProductionTLSPlatformGRPCBoundary(t *testing.T) {
	view := flowActionsTestView(
		t,
		flowActionsTestComponent(t, invocableActionKind, "bootstrap_standard_actions", standardActionsBehavior(t)),
		flowActionsTestComponent(t, flowComponentKind, "Std_ConvertLead", flowBehavior("Convert", true, "", "")),
		flowActionsTestComponent(t, flowComponentKind, "Std_ConvertLeadPreview", flowBehavior("Preview", false, "", "")),
		flowActionsTestComponent(t, flowComponentKind, "Std_CreateQuote", flowBehavior("Quote", true, "", "")),
		flowActionsTestComponent(t, flowComponentKind, "Std_SendRecordEmail", flowBehavior("Email", false, "", "")),
	)
	permissions := standardActionsPermissions()
	target, caFile, server, cleanup := startStandardActionGRPCServer(t, view, permissions)
	defer cleanup()

	startupCtx, cancelStartup := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancelStartup()
	platform, err := runtimegrpc.DialPlatformRuntime(startupCtx, runtimegrpc.RuntimeClientConfig{
		Target: target, AppInstallationID: view.AppInstallationID,
		WorkloadToken: "workload-secret", CAFile: caFile, ServerName: "runtime.test",
		AttemptTimeout: time.Second,
	})
	if err != nil {
		t.Fatal(err)
	}
	defer platform.Close()

	service := standardActionsTestService(
		t,
		permissions,
		func(context.Context, string, flowsemantic.CatalogActionRequest) (flowsemantic.ResponseV2, error) {
			t.Fatal("preview reached local execute")
			return flowsemantic.ResponseV2{}, nil
		},
	)
	service.standardActions, err = NewRuntimeStandardActionsClient(
		platform.ExecuteCatalogActionV2WithAppMetadataV3,
		platform.PreviewCatalogActionWithAppMetadataV3,
		platform.GetAppMetadataViewV3,
		platform.ReadEffectivePermissions,
	)
	if err != nil {
		t.Fatal(err)
	}

	listRecorder := httptest.NewRecorder()
	service.ServeHTTP(listRecorder, flowActionsTestRequest(
		http.MethodGet,
		"/services/data/v63.0/actions/standard?targetObject=Lead",
		"",
	))
	if listRecorder.Code != http.StatusOK ||
		!strings.Contains(listRecorder.Body.String(), `"name":"convertLead"`) {
		t.Fatalf("list status=%d body=%s", listRecorder.Code, listRecorder.Body.String())
	}

	executeRecorder := httptest.NewRecorder()
	service.ServeHTTP(executeRecorder, flowActionsTestRequest(
		http.MethodPost,
		"/services/data/v63.0/actions/standard/convertLead",
		`{"inputs":[{"leadId":"00Qgrpc001"}]}`,
	))
	if executeRecorder.Code != http.StatusOK ||
		!strings.Contains(executeRecorder.Body.String(), `"contactId":"003grpc001"`) {
		t.Fatalf("execute status=%d body=%s", executeRecorder.Code, executeRecorder.Body.String())
	}

	server.mu.Lock()
	server.executeResponse = &flowsemantic.ResponseV2{
		ExecutionID: "grpc-standard-action-validation",
		Errors: []flowsemantic.ExecutionError{
			{
				Code:    "VALIDATION_ERROR",
				Message: `Field 'AccountSource' value "RocketReach Sync" is not an active picklist option.`,
				Fields:  []string{"AccountSource"},
			},
			{
				Code:    "FIELD_CUSTOM_VALIDATION_EXCEPTION",
				Message: "The converted account does not satisfy the customer policy.",
				Fields:  []string{"Type"},
			},
		},
		Receipts: standardActionGRPCReceipts(),
	}
	server.mu.Unlock()
	failedRecorder := httptest.NewRecorder()
	service.ServeHTTP(failedRecorder, flowActionsTestRequest(
		http.MethodPost,
		"/services/data/v63.0/actions/standard/convertLead",
		`{"inputs":[{"leadId":"00Qgrpc002"}]}`,
	))
	var failedResults []flowActionExecutionResult
	if failedRecorder.Code != http.StatusOK ||
		json.Unmarshal(failedRecorder.Body.Bytes(), &failedResults) != nil ||
		len(failedResults) != 1 || failedResults[0].IsSuccess ||
		failedResults[0].ActionName != "convertLead" ||
		len(failedResults[0].Errors) != 2 ||
		failedResults[0].Errors[0].StatusCode != "VALIDATION_ERROR" ||
		failedResults[0].Errors[0].Message != `Field 'AccountSource' value "RocketReach Sync" is not an active picklist option.` ||
		len(failedResults[0].Errors[0].Fields) != 1 ||
		failedResults[0].Errors[0].Fields[0] != "AccountSource" ||
		failedResults[0].Errors[1].StatusCode != "FIELD_CUSTOM_VALIDATION_EXCEPTION" ||
		failedResults[0].Errors[1].Message != "The converted account does not satisfy the customer policy." ||
		len(failedResults[0].Errors[1].Fields) != 1 ||
		failedResults[0].Errors[1].Fields[0] != "Type" {
		t.Fatalf("failed execute status=%d body=%s", failedRecorder.Code, failedRecorder.Body.String())
	}

	recorder := httptest.NewRecorder()
	service.ServeHTTP(recorder, flowActionsTestRequest(
		http.MethodPost,
		"/services/data/v63.0/actions/standard/convertLead/output-preview",
		`{"recordId":"00Qgrpc001"}`,
	))
	if recorder.Code != http.StatusOK {
		t.Fatalf("preview status=%d body=%s", recorder.Code, recorder.Body.String())
	}

	server.mu.Lock()
	defer server.mu.Unlock()
	// Execute and output-preview use the same bearer and exact Flow capability
	// closure in one page burst, so api-host reuses one opaque delegation. The
	// earlier permission projection remains two disjoint least-privilege reads:
	// global system/custom facts and the Catalog-authored object guard closure.
	if server.identityCalls != 2 || server.catalogCalls != 4 ||
		server.permissionCalls != 2 || server.executeCalls != 2 || server.previewCalls != 1 ||
		server.executeRequest.CatalogAction.ActionRef != "convertLead" ||
		server.executeRequest.MinCatalogRevision == nil ||
		*server.executeRequest.MinCatalogRevision != view.Receipts.CatalogRevision ||
		server.previewRequest.RecordID != "00Qgrpc001" ||
		server.previewRequest.CatalogAction.ActionRef != "convertLead" ||
		server.previewRequest.MinCatalogRevision == nil ||
		*server.previewRequest.MinCatalogRevision != view.Receipts.CatalogRevision {
		t.Fatalf(
			"identity/catalog/permission/execute/preview=%d/%d/%d/%d/%d execute=%#v preview=%#v",
			server.identityCalls, server.catalogCalls,
			server.permissionCalls, server.executeCalls, server.previewCalls,
			server.executeRequest, server.previewRequest,
		)
	}
}

func startStandardActionGRPCServer(
	t *testing.T,
	view catalogsemantic.AppMetadataView,
	permissions permissionsemantic.Response,
) (string, string, *standardActionGRPCServer, func()) {
	t.Helper()
	serverCredentials, caPEM := recordFilesTestCredentials(t)
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	grpcServer := grpc.NewServer(grpc.Creds(serverCredentials))
	implementation := &standardActionGRPCServer{view: view, permissions: permissions}
	runtimev1.RegisterIdentityContextServiceServer(grpcServer, implementation)
	catalogv1.RegisterConsumerViewServiceServer(grpcServer, implementation)
	catalogv1.RegisterAppMetadataViewV3ServiceServer(grpcServer, implementation)
	runtimev1.RegisterEffectivePermissionServiceServer(grpcServer, implementation)
	runtimev1.RegisterFlowExecutionServiceServer(grpcServer, implementation)
	go func() { _ = grpcServer.Serve(listener) }()
	caFile := filepath.Join(t.TempDir(), "runtime-ca.pem")
	if err := os.WriteFile(caFile, caPEM, 0o600); err != nil {
		t.Fatal(err)
	}
	return "passthrough:///" + listener.Addr().String(), caFile, implementation, func() {
		grpcServer.GracefulStop()
		_ = listener.Close()
	}
}

func standardActionGRPCReceipts() flowsemantic.Receipts {
	return flowsemantic.Receipts{
		ProtocolVersion: "1.0", CapabilityVersion: flowsemantic.CapabilityName,
		CatalogRevision: 17, SchemaGeneration: 8, EngineGeneration: 9, SecurityEpoch: 3,
	}
}
