package approvals

import (
	"errors"
	"fmt"
	"strings"
	"sync"

	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
)

type ApprovalStep uint16

type ApprovalWorkflowKey string

func (k ApprovalWorkflowKey) String() string {
	return string(k)
}

type NextStepCallback func(current ApprovalStep, decision entity.ApprovalAction) (ApprovalStep, bool)

var (
	approvalActions = map[entity.ApprovalAction]struct{}{
		entity.ApprovalActionApproved: {},
		entity.ApprovalActionRejected: {},
		entity.ApprovalActionCreated:  {},
		entity.ApprovalActionUpdated:  {},
	}

	approvalWorkflows   = make(map[ApprovalWorkflowKey]map[ApprovalStep]string)
	approvalWorkflowsMu sync.RWMutex
)

// WorkflowConstants prepares the registered workflows for exposure via constants endpoints.
func WorkflowConstants() map[string]map[string]string {
	approvalWorkflowsMu.RLock()
	defer approvalWorkflowsMu.RUnlock()

	if len(approvalWorkflows) == 0 {
		return nil
	}

	result := make(map[string]map[string]string, len(approvalWorkflows))
	for workflow, steps := range approvalWorkflows {
		if len(steps) == 0 {
			continue
		}
		stepMap := make(map[string]string, len(steps))
		for step, label := range steps {
			stepMap[fmt.Sprintf("%d", step)] = label
		}
		result[workflow.String()] = stepMap
	}
	if len(result) == 0 {
		return nil
	}
	return result
}

// RegisterWorkflowSteps stores the available steps for a workflow key (usually matching approvable type).
func RegisterWorkflowSteps(workflow ApprovalWorkflowKey, steps map[ApprovalStep]string) error {
	workflowStr := strings.TrimSpace(workflow.String())
	if workflowStr == "" {
		return errors.New("workflow key is required")
	}
	if len(steps) == 0 {
		return fmt.Errorf("no steps defined for workflow %q", workflowStr)
	}

	copied := make(map[ApprovalStep]string, len(steps))
	for step, label := range steps {
		if step == 0 {
			return fmt.Errorf("workflow %q contains step 0 which is not allowed", workflowStr)
		}
		trimmed := strings.TrimSpace(label)
		if trimmed == "" {
			return fmt.Errorf("workflow %q contains empty label for step %d", workflowStr, step)
		}
		copied[step] = trimmed
	}

	approvalWorkflowsMu.Lock()
	defer approvalWorkflowsMu.Unlock()
	approvalWorkflows[ApprovalWorkflowKey(workflowStr)] = copied
	return nil
}

// WorkflowSteps returns the steps registered for the given workflow key.
func WorkflowSteps(workflow ApprovalWorkflowKey) map[ApprovalStep]string {
	approvalWorkflowsMu.RLock()
	defer approvalWorkflowsMu.RUnlock()

	workflowStr := strings.TrimSpace(workflow.String())
	if workflowStr == "" {
		return nil
	}

	steps, ok := approvalWorkflows[ApprovalWorkflowKey(workflowStr)]
	if !ok || len(steps) == 0 {
		return nil
	}

	copied := make(map[ApprovalStep]string, len(steps))
	for step, label := range steps {
		copied[step] = label
	}
	return copied
}

// ApprovalStepName fetches the label for the target step inside the workflow.
func ApprovalStepName(workflow ApprovalWorkflowKey, step ApprovalStep) (string, bool) {
	steps := WorkflowSteps(workflow)
	if len(steps) == 0 {
		return "", false
	}
	label, ok := steps[step]
	return label, ok
}

// ValidateApprovalStep ensures the workflow contains the provided step.
func ValidateApprovalStep(workflow ApprovalWorkflowKey, step ApprovalStep) error {
	if _, ok := ApprovalStepName(workflow, step); ok {
		return nil
	}
	return fmt.Errorf("invalid approval step %d for workflow %s", step, workflow)
}

// IsValidApprovalAction reports whether the action is supported.
func IsValidApprovalAction(action entity.ApprovalAction) bool {
	_, ok := approvalActions[action]
	return ok
}

// NewApproval creates an approval record for the given approvable target.
func NewApproval(workflow ApprovalWorkflowKey, approvableId uint, step ApprovalStep, action *entity.ApprovalAction, actorId uint, note *string) (*entity.Approval, error) {
	if approvableId == 0 {
		return nil, errors.New("approvable id is required")
	}

	workflowStr := strings.TrimSpace(workflow.String())
	if workflowStr == "" {
		return nil, errors.New("approval workflow key is required")
	}

	key := ApprovalWorkflowKey(workflowStr)

	if err := ValidateApprovalStep(key, step); err != nil {
		return nil, err
	}

	var actionPtr *entity.ApprovalAction
	if action != nil {
		if !IsValidApprovalAction(*action) {
			return nil, fmt.Errorf("invalid approval action %q", *action)
		}
		actionCopy := *action
		actionPtr = &actionCopy
	}

	if actorId == 0 {
		return nil, errors.New("actor id is required")
	}

	var notes *string
	if note != nil {
		trimmed := strings.TrimSpace(*note)
		if trimmed != "" {
			notes = &trimmed
		}
	}

	actor := actorId
	var stepName string
	if label, ok := ApprovalStepName(key, step); ok {
		labelCopy := label
		stepName = labelCopy
	}

	return &entity.Approval{
		ApprovableType: workflowStr,
		ApprovableId:   approvableId,
		StepNumber:     uint16(step),
		StepName:       stepName,
		Action:         actionPtr,
		Notes:          notes,
		ActionBy:       &actor,
	}, nil
}

// SetApprovalAction updates the approval action, notes, and optionally advances to another step.
func SetApprovalAction(approval *entity.Approval, action entity.ApprovalAction, actorId uint, note *string, nextStep NextStepCallback) error {
	if approval == nil {
		return errors.New("approval is nil")
	}
	if !IsValidApprovalAction(action) {
		return fmt.Errorf("invalid approval action %q", action)
	}
	if actorId == 0 {
		return errors.New("actor id is required for approval decision")
	}

	act := action
	approval.Action = &act
	approval.ActionBy = &actorId

	if note != nil {
		trimmed := strings.TrimSpace(*note)
		if trimmed == "" {
			approval.Notes = nil
		} else {
			approval.Notes = &trimmed
		}
	} else {
		approval.Notes = nil
	}

	if nextStep != nil {
		current := ApprovalStep(approval.StepNumber)
		if proposed, ok := nextStep(current, action); ok {
			if err := ValidateApprovalStep(ApprovalWorkflowKey(approval.ApprovableType), proposed); err != nil {
				return err
			}
			approval.StepNumber = uint16(proposed)
		}
	}

	if label, ok := ApprovalStepName(ApprovalWorkflowKey(approval.ApprovableType), ApprovalStep(approval.StepNumber)); ok {
		labelCopy := label
		approval.StepName = labelCopy
	}

	return nil
}

// Approve marks the approval as approved by the given actor, applying the optional step callback.
func Approve(approval *entity.Approval, actorId uint, note *string, nextStep NextStepCallback) error {
	return SetApprovalAction(approval, entity.ApprovalActionApproved, actorId, note, nextStep)
}

// Reject marks the approval as rejected by the given actor, applying the optional step callback.
func Reject(approval *entity.Approval, actorId uint, note *string, nextStep NextStepCallback) error {
	return SetApprovalAction(approval, entity.ApprovalActionRejected, actorId, note, nextStep)
}
