package sfcrmappservice

import (
	"context"
	"fmt"
	"net/http"
	"strings"

	crmwire "github.com/pingcap-inc/nexuscrm-app-packs/sfcrm/wire"
	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"
)

const approvalNotificationFlowPermissionModifyMetadata = "ModifyMetadata"

type RuntimeReadApprovalNotificationFlowsFunc func(
	context.Context,
	string,
	approvalnotificationsemantic.Request,
) (approvalnotificationsemantic.Response, error)

// RuntimeApprovalNotificationFlowsClient composes the L1-owned administrator
// permission with the neutral descriptor read. Runtime owns Flow eligibility;
// this client owns only Salesforce route authorization and projection.
type RuntimeApprovalNotificationFlowsClient struct {
	readFlows       RuntimeReadApprovalNotificationFlowsFunc
	readPermissions RuntimeReadEffectivePermissionsFunc
}

func NewRuntimeApprovalNotificationFlowsClient(
	readFlows RuntimeReadApprovalNotificationFlowsFunc,
	readPermissions RuntimeReadEffectivePermissionsFunc,
) (*RuntimeApprovalNotificationFlowsClient, error) {
	if readFlows == nil || readPermissions == nil {
		return nil, fmt.Errorf("sfcrm AppService: approval notification Flow and effective-permission Runtime ports are required")
	}
	return &RuntimeApprovalNotificationFlowsClient{
		readFlows: readFlows, readPermissions: readPermissions,
	}, nil
}

func (client *RuntimeApprovalNotificationFlowsClient) Read(
	ctx context.Context,
	bearer string,
) (approvalnotificationsemantic.Response, error) {
	request := approvalnotificationsemantic.Request{}
	if err := approvalnotificationsemantic.ValidateRequest(ctx, request); err != nil {
		return approvalnotificationsemantic.Response{}, err
	}
	if client == nil || client.readFlows == nil || client.readPermissions == nil {
		return approvalnotificationsemantic.Response{}, &commonsemantic.Failure{
			Category: commonsemantic.FailureUnavailable,
		}
	}
	permissions, err := client.readPermissions(
		ctx, bearer, systemPermissionAuthorityRequest(),
	)
	if err != nil {
		return approvalnotificationsemantic.Response{}, normalizeRuntimeError(err)
	}
	if err := permissionsemantic.ValidateResponse(permissions); err != nil {
		return approvalnotificationsemantic.Response{}, err
	}
	if !hasSystemPermission(
		permissions.SystemPermissions,
		approvalNotificationFlowPermissionModifyMetadata,
	) {
		return approvalnotificationsemantic.Response{}, &commonsemantic.Failure{
			Category: commonsemantic.FailureForbidden,
		}
	}
	response, err := client.readFlows(ctx, bearer, request)
	if err != nil {
		return approvalnotificationsemantic.Response{}, normalizeRuntimeError(err)
	}
	if err := approvalnotificationsemantic.ValidateResponseForRequest(request, response); err != nil {
		return approvalnotificationsemantic.Response{}, err
	}
	// The neutral descriptor port reads current repository metadata and does
	// not claim a Catalog revision. SecurityEpoch is the shared authority axis
	// that prevents a permission read from being combined with a descriptor
	// response admitted under different caller security state.
	if response.Receipts.SecurityEpoch != permissions.Receipts.SecurityEpoch {
		return approvalnotificationsemantic.Response{}, &commonsemantic.Failure{
			Category:         commonsemantic.FailureStaleState,
			StaleAxis:        commonsemantic.StaleAxisSecurity,
			RequiredRevision: permissions.Receipts.SecurityEpoch,
			ServedRevision:   response.Receipts.SecurityEpoch,
		}
	}
	return response, nil
}

func matchApprovalNotificationFlowsRoute(path string) (string, bool) {
	parts := strings.Split(strings.Trim(path, "/"), "/")
	if len(parts) != 6 || parts[0] != "services" || parts[1] != "data" ||
		parts[3] != "process" || parts[4] != "approvals" ||
		parts[5] != "notification-flows" {
		return "", false
	}
	if _, supported := recordViewVersions[parts[2]]; !supported {
		return "", false
	}
	return parts[2], true
}

type approvalNotificationFlowActionDTO struct {
	Label       string `json:"label"`
	Name        string `json:"name"`
	Type        string `json:"type"`
	URL         string `json:"url"`
	Description string `json:"description,omitempty"`
}

type approvalNotificationFlowsDTO struct {
	Actions []approvalNotificationFlowActionDTO `json:"actions"`
}

func (service *Service) serveApprovalNotificationFlows(
	writer http.ResponseWriter,
	request *http.Request,
) bool {
	version, matched := matchApprovalNotificationFlowsRoute(request.URL.EscapedPath())
	if !matched || service.notificationFlows == nil {
		return false
	}
	service.requests.Add(1)
	if request.Method != http.MethodGet {
		service.localRejected.Add(1)
		service.failures.Add(1)
		writer.Header().Set("Allow", http.MethodGet)
		writeCRMErrorWith(
			writer, http.StatusMethodNotAllowed, crmwire.SFCodeInvalidRequest,
			"Method not allowed", writeLegacyJSON,
		)
		return true
	}
	if service.draining.Load() {
		service.drainRejected.Add(1)
		service.failures.Add(1)
		writeCRMErrorWith(
			writer, http.StatusServiceUnavailable, crmwire.SFCodeUnknownException,
			"Service is draining", writeLegacyJSON,
		)
		return true
	}
	bearer, ok := endUserBearer(request.Header.Values("Authorization"))
	if !ok {
		service.localRejected.Add(1)
		service.failures.Add(1)
		writeCRMErrorWith(
			writer, http.StatusUnauthorized, crmwire.SFCodeInvalidSessionID,
			"Session expired or invalid", writeLegacyJSON,
		)
		return true
	}
	service.inFlight.Add(1)
	defer service.inFlight.Add(-1)
	ctx, cancel := context.WithTimeout(request.Context(), service.requestTimeout)
	defer cancel()
	response, err := service.notificationFlows.Read(ctx, bearer)
	if err != nil {
		service.failures.Add(1)
		writeRuntimeFailureWith(writer, err, writeLegacyJSON)
		return true
	}
	actions := make([]approvalNotificationFlowActionDTO, 0, len(response.Flows))
	for _, flow := range response.Flows {
		actions = append(actions, approvalNotificationFlowActionDTO{
			Label: flow.Label, Name: flow.Name, Type: "FLOW",
			URL: fmt.Sprintf(
				"/services/data/%s/actions/custom/flow/%s", version, flow.Name,
			),
			Description: flow.Description,
		})
	}
	service.successes.Add(1)
	writeLegacyJSON(writer, http.StatusOK, approvalNotificationFlowsDTO{Actions: actions})
	return true
}
