package orchestrator

import (
	"errors"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
	"github.com/tidbcloud/AgentTeam/internal/workflow/publicerr"
	"github.com/tidbcloud/AgentTeam/internal/workflow/run"
)

// Callback validation failure codes. These are the stable public codes the
// workflow step surface projects for typed callback validation failures. Each
// category keeps a distinct code so callers never have to collapse an unknown
// failure back to the single fixed step_output_invalid message.
const (
	// CallbackValidationCodeOutputContract covers output contract violations
	// (undeclared/missing fields, type/enum/scope mismatches, defaults).
	CallbackValidationCodeOutputContract = "step_output_contract_invalid"
	// CallbackValidationCodeWorkScope covers the agent-selected or fixed
	// collaboration workScope validation failures.
	CallbackValidationCodeWorkScope = "step_work_scope_invalid"
	// CallbackValidationCodeWorkScopeUnauthorized covers scope authorization
	// failures where the completing Agent cannot use the requested scope.
	CallbackValidationCodeWorkScopeUnauthorized = "step_work_scope_unauthorized"
	// CallbackValidationCodeWorkItems covers WorkItem shape, budget, key, index,
	// field, responder, and DAG validation failures.
	CallbackValidationCodeWorkItems = "step_work_items_invalid"
	// CallbackValidationCodeWorkItemReference covers dependency-reference syntax
	// and static contract diagnostics inside WorkItem instructions.
	CallbackValidationCodeWorkItemReference = "step_work_item_reference_invalid"
	// CallbackValidationCodeOutputBudget covers the combined durable payload
	// budget exceeded by the callback result/output.
	CallbackValidationCodeOutputBudget = "step_output_budget_exceeded"
)

// CallbackValidationError is the typed callback validation failure. It carries
// the stable public code and the typed public diagnostics, and implements
// publicerr.DomainError so the centralized Workflow classifier projects the
// concrete code and diagnostics instead of a fixed step_output_invalid message.
// It unwraps to storeerr.ErrBadRequest (status 400) and to the legacy
// ErrCallbackOutputInvalid sentinel so errors.Is checks on the pre-typed
// boundary keep working.
type CallbackValidationError struct {
	Code        string
	Diagnostics []publicerr.Diagnostic
	Cause       error
}

func (e *CallbackValidationError) Error() string {
	if e == nil {
		return ""
	}
	if len(e.Diagnostics) > 0 && strings.TrimSpace(e.Diagnostics[0].Message) != "" {
		return e.Diagnostics[0].Message
	}
	if e.Cause != nil {
		return e.Cause.Error()
	}
	return "workflow Run callback output is invalid"
}

func (e *CallbackValidationError) Unwrap() error {
	if e == nil {
		return nil
	}
	return e.Cause
}

// WorkflowPublicError implements publicerr.DomainError.
func (e *CallbackValidationError) WorkflowPublicError() (string, any) {
	if e == nil {
		return "step_output_invalid", nil
	}
	return e.Code, map[string]any{"diagnostics": e.Diagnostics}
}

// callbackValidationError builds a typed callback validation failure. The cause
// is preserved in the chain and is also joined with storeerr.ErrBadRequest and
// the legacy ErrCallbackOutputInvalid sentinel.
func callbackValidationError(code string, diagnostics []publicerr.Diagnostic, cause error) error {
	if cause == nil {
		cause = errors.New("workflow Run callback output is invalid")
	}
	return &CallbackValidationError{
		Code:        code,
		Diagnostics: diagnostics,
		Cause:       errors.Join(storeerr.ErrBadRequest, ErrCallbackOutputInvalid, cause),
	}
}

// callbackDiagnosticsFromCause converts a runkernel structured validation error
// into public diagnostics. When the cause does not carry a structured
// ValidationError, it falls back to one generic diagnostic with the bounded
// message so the failure never escapes with an empty code.
func callbackDiagnosticsFromCause(cause error) []publicerr.Diagnostic {
	if cause != nil {
		if validation, ok := run.ValidationErrorDiagnostic(cause); ok {
			return []publicerr.Diagnostic{{
				Severity: publicerr.SeverityError,
				Code:     validation.Code,
				Path:     validation.Path,
				Field:    validation.Field,
				Index:    validation.Index,
				Message:  validation.Message,
			}}
		}
	}
	return []publicerr.Diagnostic{{
		Severity: publicerr.SeverityError,
		Code:     CallbackValidationCodeOutputContract,
		Message:  publicerr.BoundedProblem(cause.Error()),
	}}
}

// callbackDiagnosticsFromError extracts the diagnostics carried by an error that
// may already be a typed CallbackValidationError. It is used by the callback
// completion aggregation path so a single complete attempt can surface every
// independent validation problem at once instead of the first failure only.
func callbackDiagnosticsFromError(err error) []publicerr.Diagnostic {
	var validation *CallbackValidationError
	if errors.As(err, &validation) && validation != nil && len(validation.Diagnostics) > 0 {
		return append([]publicerr.Diagnostic(nil), validation.Diagnostics...)
	}
	return callbackDiagnosticsFromCause(err)
}

// mergeCallbackValidationDiagnostics returns a single CallbackValidationError
// carrying every independent validation diagnostic collected during callback
// completion. The top-level public code is the first sub-error's own code (or
// the caller-provided fallback), so a single aggregate failure still projects a
// stable category code while every diagnostic keeps its granular code/path.
func mergeCallbackValidationDiagnostics(fallbackCode string, diagnostics []publicerr.Diagnostic, firstCode string, cause error) error {
	if len(diagnostics) == 0 {
		return callbackValidationError(firstCode, nil, cause)
	}
	topLevel := firstCode
	if topLevel == "" {
		topLevel = fallbackCode
	}
	return callbackValidationError(topLevel, diagnostics, cause)
}

// callbackValidationCodeFromError returns the top-level code carried by a
// sub-error when it is already a typed CallbackValidationError.
func callbackValidationCodeFromError(err error) string {
	var validation *CallbackValidationError
	if errors.As(err, &validation) && validation != nil {
		return validation.Code
	}
	return ""
}

// callbackDiagnostic builds one public diagnostic from explicit
// fields. It is used by orchestrator validation paths that do not flow through a
// runkernel structured validator.
func callbackDiagnostic(code, path, field string, index *int, message string) publicerr.Diagnostic {
	return publicerr.Diagnostic{
		Severity: publicerr.SeverityError,
		Code:     code,
		Path:     path,
		Field:    field,
		Index:    index,
		Message:  publicerr.BoundedProblem(message),
	}
}
