package sfcrmappservice

import (
	"context"
	"errors"
	"net/http"
	"net/http/httptest"
	"slices"
	"strings"
	"testing"

	catalogsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	approvalnotificationsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/approvalnotificationflowv1"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	permissionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
)

func TestApprovalNotificationFlowsRoutePreservesSalesforceProjectionAndAuthority(t *testing.T) {
	permissions := approvalNotificationFlowPermissions(true, 3)
	var got approvalnotificationsemantic.Request
	service := approvalNotificationFlowTestService(t, permissions, func(
		_ context.Context,
		bearer string,
		request approvalnotificationsemantic.Request,
	) (approvalnotificationsemantic.Response, error) {
		if bearer != "user-secret" {
			t.Fatalf("bearer = %q", bearer)
		}
		got = request
		return approvalnotificationsemantic.Response{
			Flows: []approvalnotificationsemantic.FlowDescriptor{
				{Name: "Notify_Approver", Label: "Notify Approver", Description: "approval email"},
				{Name: "Notify_Requester", Label: "Notify Requester"},
			},
			Receipts: approvalNotificationFlowReceipts(3),
		}, nil
	})
	request := approvalHTTPRequest(
		http.MethodGet,
		"/services/data/v62.0/process/approvals/notification-flows",
		"",
	)
	response := httptest.NewRecorder()
	service.ServeHTTP(response, request)
	if response.Code != http.StatusOK {
		t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
	}
	want := `{"actions":[{"label":"Notify Approver","name":"Notify_Approver","type":"FLOW","url":"/services/data/v62.0/actions/custom/flow/Notify_Approver","description":"approval email"},{"label":"Notify Requester","name":"Notify_Requester","type":"FLOW","url":"/services/data/v62.0/actions/custom/flow/Notify_Requester"}]}`
	if body := strings.TrimSpace(response.Body.String()); body != want {
		t.Fatalf("body=%s\nwant=%s", body, want)
	}
	if got.MinCatalogRevision != nil {
		t.Fatalf("request = %#v, want current repository read without a fabricated Catalog revision", got)
	}
}

func TestApprovalNotificationFlowsRouteDeniesBeforeDescriptorRuntime(t *testing.T) {
	flowsCalls := 0
	service := approvalNotificationFlowTestService(
		t,
		approvalNotificationFlowPermissions(false, 3),
		func(context.Context, string, approvalnotificationsemantic.Request) (approvalnotificationsemantic.Response, error) {
			flowsCalls++
			return approvalnotificationsemantic.Response{}, nil
		},
	)
	response := httptest.NewRecorder()
	service.ServeHTTP(response, approvalHTTPRequest(
		http.MethodGet,
		"/services/data/v63.0/process/approvals/notification-flows",
		"",
	))
	if response.Code != http.StatusForbidden || flowsCalls != 0 ||
		!strings.Contains(response.Body.String(), "Insufficient access") {
		t.Fatalf("status=%d calls=%d body=%s", response.Code, flowsCalls, response.Body.String())
	}
}

func TestApprovalNotificationFlowsRouteRejectsMissingBearerAndWrongMethodLocally(t *testing.T) {
	permissionCalls := 0
	config := permissionMetadataServiceConfig(func(
		context.Context,
		string,
		permissionsemantic.Request,
	) (permissionsemantic.Response, error) {
		permissionCalls++
		return approvalNotificationFlowPermissions(true, 3), nil
	}, func(context.Context, catalogsemantic.GetRequest) (catalogsemantic.AppMetadataView, error) {
		return catalogsemantic.AppMetadataView{}, nil
	})
	config.RuntimeReadNotificationFlows = func(
		context.Context,
		string,
		approvalnotificationsemantic.Request,
	) (approvalnotificationsemantic.Response, error) {
		t.Fatal("locally rejected request reached descriptor Runtime")
		return approvalnotificationsemantic.Response{}, nil
	}
	service, err := NewService(config)
	if err != nil {
		t.Fatal(err)
	}
	for _, test := range []struct {
		name       string
		request    *http.Request
		wantStatus int
	}{
		{
			name: "missing bearer",
			request: httptest.NewRequest(
				http.MethodGet,
				"/services/data/v63.0/process/approvals/notification-flows",
				nil,
			),
			wantStatus: http.StatusUnauthorized,
		},
		{
			name: "wrong method",
			request: approvalHTTPRequest(
				http.MethodPost,
				"/services/data/v63.0/process/approvals/notification-flows",
				"",
			),
			wantStatus: http.StatusMethodNotAllowed,
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			response := httptest.NewRecorder()
			service.ServeHTTP(response, test.request)
			if response.Code != test.wantStatus {
				t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
			}
		})
	}
	if permissionCalls != 0 {
		t.Fatalf("permission calls = %d, want 0", permissionCalls)
	}
}

func TestApprovalNotificationFlowsRouteFailsClosedAcrossSecurityEpochs(t *testing.T) {
	service := approvalNotificationFlowTestService(
		t,
		approvalNotificationFlowPermissions(true, 3),
		func(context.Context, string, approvalnotificationsemantic.Request) (approvalnotificationsemantic.Response, error) {
			return approvalnotificationsemantic.Response{
				Flows:    []approvalnotificationsemantic.FlowDescriptor{},
				Receipts: approvalNotificationFlowReceipts(4),
			}, nil
		},
	)
	response := httptest.NewRecorder()
	service.ServeHTTP(response, approvalHTTPRequest(
		http.MethodGet,
		"/services/data/v63.0/process/approvals/notification-flows",
		"",
	))
	if response.Code != http.StatusServiceUnavailable ||
		strings.Contains(response.Body.String(), `"actions"`) {
		t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
	}
}

func TestApprovalNotificationFlowsRouteExposesRuntimeFailureInsteadOfEmptySuccess(t *testing.T) {
	service := approvalNotificationFlowTestService(
		t,
		approvalNotificationFlowPermissions(true, 3),
		func(context.Context, string, approvalnotificationsemantic.Request) (approvalnotificationsemantic.Response, error) {
			return approvalnotificationsemantic.Response{}, &commonsemantic.Failure{
				Category: commonsemantic.FailureUnavailable,
			}
		},
	)
	response := httptest.NewRecorder()
	service.ServeHTTP(response, approvalHTTPRequest(
		http.MethodGet,
		"/services/data/v63.0/process/approvals/notification-flows",
		"",
	))
	if response.Code != http.StatusServiceUnavailable ||
		strings.Contains(response.Body.String(), `"actions"`) {
		t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
	}
}

func TestApprovalNotificationFlowsPortIsOptionalButRequiresPermissionPort(t *testing.T) {
	base := permissionMetadataServiceConfig(nil, nil)
	base.RuntimeReadNotificationFlows = func(
		context.Context,
		string,
		approvalnotificationsemantic.Request,
	) (approvalnotificationsemantic.Response, error) {
		return approvalnotificationsemantic.Response{}, nil
	}
	if service, err := NewService(base); err == nil || service != nil {
		t.Fatalf("partial notification Flow composition = %#v, %v", service, err)
	}
	base.RuntimeReadNotificationFlows = nil
	service, err := NewService(base)
	if err != nil {
		t.Fatal(err)
	}
	response := httptest.NewRecorder()
	service.ServeHTTP(response, approvalHTTPRequest(
		http.MethodGet,
		"/services/data/v63.0/process/approvals/notification-flows",
		"",
	))
	if response.Code != http.StatusNotFound {
		t.Fatalf("uninstalled status=%d body=%s", response.Code, response.Body.String())
	}
}

func TestApprovalNotificationFlowsClientRejectsDeadlineFreeContext(t *testing.T) {
	client, err := NewRuntimeApprovalNotificationFlowsClient(
		func(context.Context, string, approvalnotificationsemantic.Request) (approvalnotificationsemantic.Response, error) {
			t.Fatal("deadline-free request reached descriptor Runtime")
			return approvalnotificationsemantic.Response{}, nil
		},
		func(context.Context, string, permissionsemantic.Request) (permissionsemantic.Response, error) {
			t.Fatal("deadline-free request reached permission Runtime")
			return permissionsemantic.Response{}, nil
		},
	)
	if err != nil {
		t.Fatal(err)
	}
	_, err = client.Read(context.Background(), "user-secret")
	var failure *commonsemantic.Failure
	if !errors.As(err, &failure) || failure.Category != commonsemantic.FailureInvalidRequest {
		t.Fatalf("error = %#v, want INVALID_REQUEST", err)
	}
}

func approvalNotificationFlowTestService(
	t *testing.T,
	permissions permissionsemantic.Response,
	readFlows RuntimeReadApprovalNotificationFlowsFunc,
) *Service {
	t.Helper()
	config := permissionMetadataServiceConfig(func(
		_ context.Context,
		bearer string,
		request permissionsemantic.Request,
	) (permissionsemantic.Response, error) {
		if bearer != "user-secret" || request.TargetPrincipalID != "" ||
			request.MinCatalogRevision != nil || request.Selection == nil ||
			!slices.Equal(request.Selection.Sections, []permissionsemantic.Section{
				permissionsemantic.SectionSystemPermissions,
			}) || len(request.Selection.ResourceTypes) != 0 {
			t.Fatalf("effective-permission request = %#v, bearer=%q", request, bearer)
		}
		return permissions, nil
	}, func(context.Context, catalogsemantic.GetRequest) (catalogsemantic.AppMetadataView, error) {
		return catalogsemantic.AppMetadataView{}, nil
	})
	config.RuntimeReadNotificationFlows = readFlows
	service, err := NewService(config)
	if err != nil {
		t.Fatal(err)
	}
	return service
}

func approvalNotificationFlowPermissions(
	granted bool,
	securityEpoch uint64,
) permissionsemantic.Response {
	response := validPermissionResponse(17, securityEpoch)
	response.SystemPermissions = append(response.SystemPermissions, permissionsemantic.PermissionGrant{
		PermissionID: approvalNotificationFlowPermissionModifyMetadata,
		Granted:      granted,
	})
	slices.SortFunc(response.SystemPermissions, func(left, right permissionsemantic.PermissionGrant) int {
		return strings.Compare(left.PermissionID, right.PermissionID)
	})
	return response
}

func approvalNotificationFlowReceipts(
	securityEpoch uint64,
) approvalnotificationsemantic.Receipts {
	return approvalnotificationsemantic.Receipts{
		ProtocolVersion: "1.0", SecurityEpoch: securityEpoch,
		CapabilityVersion: approvalnotificationsemantic.CapabilityName,
	}
}
