package main

import (
	"fmt"

	module "bijection.dev/module"
	"bijection.dev/product"
)

const waitCeilingMS int64 = 2678400000

func init() { module.Register(product.DefineCollectSignature(run)) }
func main() {}

func run(_ module.Context, input product.WorkflowCollectSignatureInput) (product.WorkflowCollectSignatureResult, error) {
	requestID := input.RequestID
	if requestID == "" {
		return nil, fmt.Errorf("signature request_id must be nonempty")
	}
	target := product.ObjectSignatureRequestRef(requestID)
	delivered, err := product.Actions.SendSignatureRequest(product.ActionSendSignatureRequestInput{}, target)
	if err != nil {
		return nil, err
	}
	deliveryID, err := acknowledgedDeliveryID(delivered)
	if err != nil {
		return nil, err
	}
	if _, err := product.Actions.ConfirmSignatureDelivery(product.ActionConfirmSignatureDeliveryInput{DeliveryID: deliveryID}, target); err != nil {
		return nil, err
	}
	check, err := product.Functions.SignatureRequestTerminalCheck(product.FunctionSignatureRequestTerminalInput{RequestID: requestID})
	if err != nil {
		return nil, err
	}
	firstWait, err := product.Signals.OnboardingSignatureChanged(target, waitCeilingMS, module.SignalOptions{DeadlineAt: input.ReminderAt, Until: check})
	if err != nil {
		return nil, err
	}
	state, err := signatureState(requestID, firstWait)
	if err != nil {
		return nil, err
	}
	if outcome, done, err := finishTerminal(requestID, state, target); done || err != nil {
		return outcome, err
	}
	if _, err := product.Actions.SendSignatureReminder(product.ActionSendSignatureReminderInput{}, target); err != nil {
		return nil, err
	}
	finalWait, err := product.Signals.OnboardingSignatureChanged(target, waitCeilingMS, module.SignalOptions{DeadlineAt: input.ExpiresAt, Until: check})
	if err != nil {
		return nil, err
	}
	state, err = signatureState(requestID, finalWait)
	if err != nil {
		return nil, err
	}
	if outcome, done, err := finishTerminal(requestID, state, target); done || err != nil {
		return outcome, err
	}
	if state.Status != "requested" && state.Status != "sending" && state.Status != "sent" {
		return nil, fmt.Errorf("signature request has unexpected state %q", state.Status)
	}
	if _, err := product.Actions.ExpireSignatureRequest(product.ActionExpireSignatureRequestInput{}, target); err != nil {
		return nil, err
	}
	return product.WorkflowCollectSignatureExpiredResult{RequestID: requestID}, nil
}

// acknowledgedDeliveryID reads the signature provider's own declared delivery
// identity. Queuing the request only makes the round sending; this typed
// acknowledgement is the evidence the confirmation Action records to make it
// sent, so the object never claims a delivery that failed.
func acknowledgedDeliveryID(delivered product.ActionSendSignatureRequestResult) (string, error) {
	delivery := delivered.Acknowledgements.SignatureDeliverySendSignatureRequest
	if !delivery.IsSet || delivery.IsNull {
		return "", fmt.Errorf("signature request recorded no provider acknowledgement")
	}
	deliveryID := delivery.Value.DeliveryID
	if !deliveryID.IsSet || deliveryID.IsNull || deliveryID.Value == "" {
		return "", fmt.Errorf("signature delivery recorded no acknowledged delivery identity")
	}
	return deliveryID.Value, nil
}

// A wait ends in exactly one closed way. Only a satisfied condition observed a
// dataset transaction, so only that alternative pins this read to a basis; a
// received occurrence and an elapsed deadline read the latest published one.
func signatureState(requestID string, observation product.SignalObservation) (product.FunctionSignatureRequestStateResultValue, error) {
	basis := ""
	if observation.ConditionDatasetTransaction.IsSet && !observation.ConditionDatasetTransaction.IsNull {
		basis = observation.ConditionDatasetTransaction.Value
	}
	state, err := product.Functions.SignatureRequestState(product.FunctionSignatureRequestStateInput{RequestID: requestID}, module.FunctionOptions{DatasetTransaction: basis})
	return state.Value, err
}

func finishTerminal(requestID string, state product.FunctionSignatureRequestStateResultValue, target product.ObjectSignatureRequestRef) (product.WorkflowCollectSignatureResult, bool, error) {
	switch state.Status {
	case "signed":
		evidence := state.SignatureEvidenceFileID
		if !evidence.IsSet || evidence.IsNull || evidence.Value == "" {
			return nil, true, fmt.Errorf("signed request omitted signature evidence")
		}
		if _, err := product.Actions.ActivateSignedAgreement(product.ActionActivateSignedAgreementInput{DocumentRevision: state.DocumentRevision, SignatureEvidence: evidence.Value}, target); err != nil {
			return nil, true, err
		}
		return product.WorkflowCollectSignatureSignedResult{RequestID: requestID}, true, nil
	case "activated":
		return product.WorkflowCollectSignatureSignedResult{RequestID: requestID}, true, nil
	case "cancelled":
		return product.WorkflowCollectSignatureCancelledResult{RequestID: requestID}, true, nil
	case "expired":
		return product.WorkflowCollectSignatureExpiredResult{RequestID: requestID}, true, nil
	default:
		return nil, false, nil
	}
}
