package exec

import (
	"bytes"
	"encoding/json"
	"fmt"
)

// workflowOperationRisk is the closed side-effect classification for the
// operation discriminator nested inside the four public Workflow Exec
// actions. Keep this table as the single source for both result-budget receipt
// eligibility and transport retry/idempotency behavior.
type workflowOperationRisk uint8

const (
	workflowOperationUnknown workflowOperationRisk = iota
	workflowOperationRead
	workflowOperationMutation
)

type workflowOperationClassification struct {
	Object    string
	Operation string
	Risk      workflowOperationRisk
}

// workflowOperationClassifications is intentionally table-driven and exhaustive
// against the operation branches in workflow_input_schema.go. An operation is
// never inferred from a mutation-looking verb or from the broad action
// permission; unknown values fail closed as reads with no applied receipt and
// no idempotent replay path.
var workflowOperationClassifications = []workflowOperationClassification{
	{Object: "plan", Operation: "list", Risk: workflowOperationRead},
	{Object: "plan", Operation: "show", Risk: workflowOperationRead},
	{Object: "plan", Operation: "version", Risk: workflowOperationRead},
	{Object: "plan", Operation: "validate", Risk: workflowOperationRead},
	{Object: "plan", Operation: "create", Risk: workflowOperationMutation},
	{Object: "plan", Operation: "update", Risk: workflowOperationMutation},
	{Object: "plan", Operation: "archive", Risk: workflowOperationMutation},
	{Object: "plan", Operation: "publish", Risk: workflowOperationMutation},
	{Object: "trigger", Operation: "list", Risk: workflowOperationRead},
	{Object: "trigger", Operation: "show", Risk: workflowOperationRead},
	{Object: "trigger", Operation: "validate", Risk: workflowOperationRead},
	{Object: "trigger", Operation: "create", Risk: workflowOperationMutation},
	{Object: "trigger", Operation: "update", Risk: workflowOperationMutation},
	{Object: "trigger", Operation: "enable", Risk: workflowOperationMutation},
	{Object: "trigger", Operation: "disable", Risk: workflowOperationMutation},
	{Object: "trigger", Operation: "archive", Risk: workflowOperationMutation},
	{Object: "run", Operation: "list", Risk: workflowOperationRead},
	{Object: "run", Operation: "show", Risk: workflowOperationRead},
	{Object: "run", Operation: "logs", Risk: workflowOperationRead},
	{Object: "run", Operation: "start", Risk: workflowOperationMutation},
	{Object: "run", Operation: "cancel", Risk: workflowOperationMutation},
	{Object: "step", Operation: "list", Risk: workflowOperationRead},
	{Object: "step", Operation: "get", Risk: workflowOperationRead},
	{Object: "step", Operation: "complete", Risk: workflowOperationMutation},
	{Object: "step", Operation: "fail", Risk: workflowOperationMutation},
	{Object: "step", Operation: "pause", Risk: workflowOperationMutation},
	{Object: "step", Operation: "park", Risk: workflowOperationMutation},
}

func classifyWorkflowOperation(object, operation string) workflowOperationRisk {
	for _, entry := range workflowOperationClassifications {
		if entry.Object == object && entry.Operation == operation {
			return entry.Risk
		}
	}
	return workflowOperationUnknown
}

func workflowOperationIsMutation(object, operation string) bool {
	return classifyWorkflowOperation(object, operation) == workflowOperationMutation
}

func workflowOperationFromRaw(raw json.RawMessage) string {
	var discriminator struct {
		Action string `json:"action"`
	}
	decoder := json.NewDecoder(bytes.NewReader(raw))
	if decoder.Decode(&discriminator) != nil {
		return ""
	}
	return discriminator.Action
}

func workflowOperationIsMutationForInput(object string, raw json.RawMessage) bool {
	return workflowOperationIsMutation(object, workflowOperationFromRaw(raw))
}

func validateWorkflowOperationMatrix(descriptors []actionDescriptor) error {
	for _, object := range []string{"plan", "trigger", "run", "step"} {
		var descriptor actionDescriptor
		found := false
		for _, candidate := range descriptors {
			if candidate.Domain == "workflow" && candidate.Name == object {
				descriptor, found = candidate, true
				break
			}
		}
		if !found {
			return fmt.Errorf("exec: workflow %s operation contract is not registered", object)
		}
		var schema struct {
			OneOf []struct {
				Properties map[string]struct {
					Const string `json:"const"`
				} `json:"properties"`
			} `json:"oneOf"`
		}
		if err := json.Unmarshal(descriptor.InputSchema, &schema); err != nil {
			return fmt.Errorf("exec: workflow %s operation contract: %w", object, err)
		}
		operations := make(map[string]struct{}, len(schema.OneOf))
		for _, branch := range schema.OneOf {
			action, ok := branch.Properties["action"]
			if !ok || action.Const == "" {
				return fmt.Errorf("exec: workflow %s operation contract has a branch without a closed action", object)
			}
			if _, duplicate := operations[action.Const]; duplicate {
				return fmt.Errorf("exec: workflow %s operation contract repeats %q", object, action.Const)
			}
			operations[action.Const] = struct{}{}
		}
		if err := workflowOperationMatrixError(object, operations); err != nil {
			return err
		}
	}
	return nil
}

func workflowOperationMatrixError(object string, operations map[string]struct{}) error {
	for _, entry := range workflowOperationClassifications {
		if entry.Object == object {
			if _, ok := operations[entry.Operation]; !ok {
				return fmt.Errorf("exec: workflow %s operation %q is missing from the operation classification table", object, entry.Operation)
			}
		}
	}
	for operation := range operations {
		if classifyWorkflowOperation(object, operation) == workflowOperationUnknown {
			return fmt.Errorf("exec: workflow %s operation %q is missing from the operation classification table", object, operation)
		}
	}
	return nil
}
