package automation

import (
	"context"
	"fmt"
	"strings"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/nexuscrm/shared/pkg/queryspec"
)

// PendingApprovalView is the backend-facing projection used by the approval queue UI.
type PendingApprovalView struct {
	WorkItemID          string `json:"workItemId"`
	ProcessInstanceID   string `json:"processInstanceId"`
	ProcessDefinitionID string `json:"processDefinitionId"`
	TargetObjectID      string `json:"targetObjectId"`
	ObjectAPIName       string `json:"objectApiName"`
	SubmittedByID       string `json:"submittedById"`
	SubmittedByName     string `json:"submittedByName"`
	SubmittedDate       string `json:"submittedDate"`
	ActorID             string `json:"actorId"`
	OriginalActorID     string `json:"originalActorId"`
	Comments            string `json:"comments,omitempty"`
	RecordName          string `json:"recordName,omitempty"`
}

// ProcessInstancePendingWorkItemSummary is the record-detail projection of a
// pending ProcessInstanceWorkitem. Action eligibility is keyed by WorkItemID
// plus ActorID; display names are best-effort enrichment.
type ProcessInstancePendingWorkItemSummary struct {
	ID                string `json:"id"`
	ActorID           string `json:"actorId"`
	ActorName         string `json:"actorName,omitempty"`
	OriginalActorID   string `json:"originalActorId,omitempty"`
	OriginalActorName string `json:"originalActorName,omitempty"`
}

// ProcessInstanceSummary is the backend-facing projection used by record detail approval UI.
type ProcessInstanceSummary struct {
	ID                  string                                  `json:"id"`
	ProcessDefinitionID string                                  `json:"processDefinitionId"`
	TargetObjectID      string                                  `json:"targetObjectId"`
	Status              string                                  `json:"status"`
	SubmittedByID       string                                  `json:"submittedById"`
	SubmittedByName     string                                  `json:"submittedByName"`
	CreatedDate         string                                  `json:"createdDate"`
	CompletedDate       string                                  `json:"completedDate,omitempty"`
	PendingWorkItems    []ProcessInstancePendingWorkItemSummary `json:"pendingWorkItems"`
}

// ApprovalStepDisplayMetadata is the record-detail-safe projection of
// approval-step display facts. It deliberately omits raw criteria formulas,
// criteria items, and raw ProcessDefinition.Metadata.
type ApprovalStepDisplayMetadata struct {
	ProcessNodeID    string `json:"processNodeId"`
	StepOrder        int    `json:"stepOrder,omitempty"`
	StepName         string `json:"stepName,omitempty"`
	StepLabel        string `json:"stepLabel,omitempty"`
	HasEntryCriteria bool   `json:"hasEntryCriteria"`
	IfCriteriaNotMet string `json:"ifCriteriaNotMet,omitempty"`
}

// ListPendingApprovals returns the current user's pending work items enriched
// with the linked record and process metadata. This read path intentionally
// uses internal service-context lookups so the UI does not depend on public
// ProcessInstance record-query visibility.
func (s *ApprovalService) ListPendingApprovals(ctx context.Context, user *models.UserSession) ([]PendingApprovalView, error) {
	ctx = constants.WithSecurityContext(ctx, constants.SystemContext, "ApprovalService: read pending approvals")

	filter := fmt.Sprintf("ActorId == '%s'", escapeApprovalFilterValue(user.ID))
	workItems, err := s.query.QueryWithFilter(ctx, constants.TableProcessInstanceWorkitem, filter, user, "CreatedDate", "desc", 200)
	if err != nil {
		return nil, err
	}

	views := make([]PendingApprovalView, 0, len(workItems.Records))
	for _, workItem := range workItems.Records {
		workItemID := approvalStringValue(workItem[constants.FieldID])
		processInstanceID := approvalStringValue(workItem["ProcessInstanceId"])
		if workItemID == "" || processInstanceID == "" {
			continue
		}

		instance, err := s.persistence.GetRaw(ctx, constants.TableProcessInstance, processInstanceID)
		if err != nil {
			if _, runtimeBound := runtimeApprovalAuthorityFromContext(ctx); runtimeBound {
				return nil, fmt.Errorf("read pending approval ProcessInstance %s: %w", processInstanceID, err)
			}
			s.logger.Warn(fmt.Sprintf("ListPendingApprovals: failed to read ProcessInstance %s: %v", processInstanceID, err))
			continue
		}
		if approvalStringValue(instance["Status"]) != constants.ApprovalStatusPending {
			continue
		}

		processDefinitionID := approvalStringValue(instance["ProcessDefinitionId"])
		targetObjectID := approvalStringValue(instance["TargetObjectId"])
		submittedByID := approvalStringValue(instance["SubmittedById"])
		submittedDate := approvalStringValue(instance["CreatedDate"])

		objectAPIName := ""
		if processDefinitionID != "" {
			if processDef, err := s.persistence.GetRaw(ctx, constants.TableProcessDefinition, processDefinitionID); err == nil {
				objectAPIName = approvalStringValue(processDef["TableEnumOrId"])
			} else if _, runtimeBound := runtimeApprovalAuthorityFromContext(ctx); runtimeBound {
				return nil, err
			}
		}

		submittedByName := ""
		if submittedByID != "" {
			if submitter, err := s.persistence.GetRaw(ctx, constants.TableUser, submittedByID); err == nil {
				submittedByName = firstNonEmptyApprovalValue(submitter["Name"], submitter["FirstName"])
			}
		}

		recordName := ""
		if objectAPIName != "" && targetObjectID != "" {
			if record, err := s.persistence.GetRaw(ctx, objectAPIName, targetObjectID); err == nil {
				recordName = firstNonEmptyApprovalValue(record["Name"], record["Subject"], record["LastName"])
			}
		}

		views = append(views, PendingApprovalView{
			WorkItemID:          workItemID,
			ProcessInstanceID:   processInstanceID,
			ProcessDefinitionID: processDefinitionID,
			TargetObjectID:      targetObjectID,
			ObjectAPIName:       objectAPIName,
			SubmittedByID:       submittedByID,
			SubmittedByName:     submittedByName,
			SubmittedDate:       submittedDate,
			ActorID:             approvalStringValue(workItem["ActorId"]),
			OriginalActorID:     approvalStringValue(workItem["OriginalActorId"]),
			Comments:            approvalStringValue(workItem["Comments"]),
			RecordName:          recordName,
		})
	}

	return views, nil
}

// GetLatestProcessInstance returns the newest approval instance for the record.
func (s *ApprovalService) GetLatestProcessInstance(ctx context.Context, recordID string, user *models.UserSession) (*ProcessInstanceSummary, error) {
	ctx = constants.WithSecurityContext(ctx, constants.SystemContext, "ApprovalService: read record approval instance")

	filter := fmt.Sprintf("TargetObjectId == '%s'", escapeApprovalFilterValue(recordID))
	result, err := s.query.QueryWithFilter(ctx, constants.TableProcessInstance, filter, user, "CreatedDate", "desc", 1)
	if err != nil {
		return nil, err
	}
	if result.TotalSize == 0 || len(result.Records) == 0 {
		return nil, nil
	}

	instance := result.Records[0]
	instanceID := approvalStringValue(instance[constants.FieldID])
	processDefinitionID := approvalStringValue(instance["ProcessDefinitionId"])
	targetObjectID := approvalStringValue(instance["TargetObjectId"])
	status := approvalStringValue(instance["Status"])
	submittedByID := approvalStringValue(instance["SubmittedById"])

	canReadTarget, accessErr := s.canUserReadApprovalTarget(ctx, processDefinitionID, targetObjectID, user, false)
	if accessErr != nil {
		return nil, accessErr
	}
	if !canReadTarget {
		return nil, nil
	}

	submittedByName := ""
	if submittedByID != "" {
		if submitter, err := s.persistence.GetRaw(ctx, constants.TableUser, submittedByID); err == nil {
			submittedByName = firstNonEmptyApprovalValue(submitter["Name"], submitter["FirstName"])
		}
	}

	pendingWorkItems := []ProcessInstancePendingWorkItemSummary{}
	if status == constants.ApprovalStatusPending && instanceID != "" {
		var pendingErr error
		pendingWorkItems, pendingErr = s.pendingWorkItemsForProcessInstance(ctx, instanceID, user)
		if pendingErr != nil {
			return nil, pendingErr
		}
	}

	return &ProcessInstanceSummary{
		ID:                  instanceID,
		ProcessDefinitionID: processDefinitionID,
		TargetObjectID:      targetObjectID,
		Status:              status,
		SubmittedByID:       submittedByID,
		SubmittedByName:     submittedByName,
		CreatedDate:         approvalStringValue(instance["CreatedDate"]),
		CompletedDate:       approvalStringValue(instance["CompletedDate"]),
		PendingWorkItems:    pendingWorkItems,
	}, nil
}

// GetRecordApprovalStepDisplayMetadata returns step-order and conditional-step
// display facts for the newest approval instance on a record. Authorization is
// record-scoped: callers only receive facts after the same target-record read
// check used by GetLatestProcessInstance.
func (s *ApprovalService) GetRecordApprovalStepDisplayMetadata(ctx context.Context, recordID string, user *models.UserSession) ([]ApprovalStepDisplayMetadata, error) {
	ctx = constants.WithSecurityContext(ctx, constants.SystemContext, "ApprovalService: read record approval step display metadata")
	recordID = strings.TrimSpace(recordID)
	if recordID == "" {
		return []ApprovalStepDisplayMetadata{}, nil
	}

	filter := fmt.Sprintf("TargetObjectId == '%s'", escapeApprovalFilterValue(recordID))
	result, err := s.query.QueryWithFilter(ctx, constants.TableProcessInstance, filter, user, "CreatedDate", "desc", 1)
	if err != nil {
		return nil, err
	}
	if result == nil || len(result.Records) == 0 {
		return []ApprovalStepDisplayMetadata{}, nil
	}

	instance := result.Records[0]
	processDefinitionID := approvalStringValue(instance["ProcessDefinitionId"])
	targetObjectID := approvalStringValue(instance["TargetObjectId"])

	canReadTarget, accessErr := s.canUserReadApprovalTarget(ctx, processDefinitionID, targetObjectID, user, false)
	if accessErr != nil {
		return nil, accessErr
	}
	if !canReadTarget {
		return []ApprovalStepDisplayMetadata{}, nil
	}

	return s.approvalStepDisplayMetadataForProcessDefinition(ctx, processDefinitionID, user)
}

func (s *ApprovalService) approvalStepDisplayMetadataForProcessDefinition(ctx context.Context, processDefinitionID string, user *models.UserSession) ([]ApprovalStepDisplayMetadata, error) {
	processDef, err := s.loadProcessDefinitionForAction(ctx, processDefinitionID, user)
	if err != nil {
		return nil, err
	}

	metadata, parseErr := s.approvalMetadataForDefinition(processDef)
	if parseErr != nil {
		return nil, parseErr
	}

	nodeFilter := fmt.Sprintf("ProcessDefinitionId == '%s'", escapeApprovalFilterValue(processDefinitionID))
	nodeResult, err := s.query.QueryWithFilter(ctx, constants.TableProcessNode, nodeFilter, user, "StepOrder", "ASC", 100)
	if err != nil {
		return nil, err
	}
	if nodeResult == nil || len(nodeResult.Records) == 0 {
		return []ApprovalStepDisplayMetadata{}, nil
	}

	facts := make([]ApprovalStepDisplayMetadata, 0, len(nodeResult.Records))
	for index, node := range nodeResult.Records {
		stepOrder := approvalIntValue(node["StepOrder"])
		if stepOrder <= 0 {
			stepOrder = index + 1
		}

		developerName := approvalStringValue(node["DeveloperName"])
		nodeName := approvalStringValue(node["Name"])
		var metadataStep *NormalizedApprovalStep
		if metadata != nil {
			metadataStep = metadata.StepForNames(developerName, nodeName)
			if metadataStep == nil && stepOrder > 0 && stepOrder <= len(metadata.Steps) {
				metadataStep = &metadata.Steps[stepOrder-1]
			}
		}

		stepName := developerName
		stepLabel := nodeName
		hasEntryCriteria := false
		behavior := ""
		if metadataStep != nil {
			if strings.TrimSpace(metadataStep.Name) != "" {
				stepName = metadataStep.Name
			}
			if strings.TrimSpace(metadataStep.Label) != "" {
				stepLabel = metadataStep.Label
			}
			hasEntryCriteria = normalizedStepCriteriaHasFilter(metadataStep.StepCriteria)
			if hasEntryCriteria {
				behavior = normalizeIfCriteriaNotMet(metadataStep.IfCriteriaNotMet)
				if behavior == "" {
					behavior = "ApproveRecord"
				}
			}
		}

		facts = append(facts, ApprovalStepDisplayMetadata{
			ProcessNodeID:    approvalStringValue(node[constants.FieldID]),
			StepOrder:        stepOrder,
			StepName:         stepName,
			StepLabel:        stepLabel,
			HasEntryCriteria: hasEntryCriteria,
			IfCriteriaNotMet: behavior,
		})
	}

	return facts, nil
}

func (s *ApprovalService) canUserReadApprovalTarget(ctx context.Context, processDefinitionID, targetObjectID string, user *models.UserSession, scanAll bool) (bool, error) {
	if user == nil || strings.TrimSpace(user.ID) == "" || strings.TrimSpace(processDefinitionID) == "" || strings.TrimSpace(targetObjectID) == "" {
		return false, nil
	}

	processDef, err := s.loadProcessDefinitionForAction(ctx, processDefinitionID, user)
	if err != nil {
		return false, err
	}
	objectAPIName := strings.TrimSpace(approvalStringValue(processDef["TableEnumOrId"]))
	if objectAPIName == "" {
		return false, nil
	}

	userReadCtx := constants.WithSecurityContext(ctx, constants.UserContext, "ApprovalService: verify target record read access")
	filter := fmt.Sprintf("Id == '%s'", escapeApprovalFilterValue(targetObjectID))
	if scanAll {
		if querySvc, ok := s.query.(interface {
			QueryWithSpec(context.Context, queryspec.QuerySpec, *models.UserSession) (*models.QueryResult, error)
		}); ok {
			result, err := querySvc.QueryWithSpec(userReadCtx, queryspec.QuerySpec{
				ObjectAPIName: objectAPIName,
				Filter:        queryspec.FormulaPredicate(filter),
				Limit:         1,
				ScanAll:       true,
			}, user)
			if err != nil {
				return false, err
			}
			return result != nil && len(result.Records) > 0, nil
		}
	}
	result, err := s.query.QueryWithFilter(userReadCtx, objectAPIName, filter, user, "", "", 1)
	if err != nil {
		return false, err
	}
	return result != nil && len(result.Records) > 0, nil
}

func (s *ApprovalService) pendingWorkItemsForProcessInstance(ctx context.Context, processInstanceID string, user *models.UserSession) ([]ProcessInstancePendingWorkItemSummary, error) {
	filter := fmt.Sprintf("ProcessInstanceId == '%s'", escapeApprovalFilterValue(processInstanceID))
	result, err := s.query.QueryWithFilter(ctx, constants.TableProcessInstanceWorkitem, filter, user, "CreatedDate", "asc", 200)
	if err != nil {
		return nil, err
	}
	if result == nil || len(result.Records) == 0 {
		return []ProcessInstancePendingWorkItemSummary{}, nil
	}

	nameCache := map[string]string{}
	resolveName := func(userID string) string {
		userID = strings.TrimSpace(userID)
		if userID == "" {
			return ""
		}
		if cached, ok := nameCache[userID]; ok {
			return cached
		}
		name := ""
		if userRecord, err := s.persistence.GetRaw(ctx, constants.TableUser, userID); err == nil {
			name = firstNonEmptyApprovalValue(userRecord["Name"], userRecord["FirstName"], userRecord["Username"])
		}
		nameCache[userID] = name
		return name
	}

	items := make([]ProcessInstancePendingWorkItemSummary, 0, len(result.Records))
	for _, workItem := range result.Records {
		workItemID := approvalStringValue(workItem[constants.FieldID])
		actorID := approvalStringValue(workItem["ActorId"])
		if workItemID == "" || actorID == "" {
			continue
		}
		originalActorID := approvalStringValue(workItem["OriginalActorId"])
		items = append(items, ProcessInstancePendingWorkItemSummary{
			ID:                workItemID,
			ActorID:           actorID,
			ActorName:         resolveName(actorID),
			OriginalActorID:   originalActorID,
			OriginalActorName: resolveName(originalActorID),
		})
	}
	return items, nil
}
