package automation

import (
	"encoding/json"
	"errors"
	"strings"
)

const (
	automationCodeSourceDefinitionPathNotFound = "source_definition_path_not_found"
	automationCodeMissingManual                = "capability_missing_manual"
	automationCodeSyntaxInvalidYAML            = "syntax_invalid_yaml"
	automationCodeUnsupportedSyntax            = "capability_unsupported_syntax"
	automationCodeCredentialSecretMissing      = "credential_secret_missing"
	automationCodePermissionDenied             = "permission_denied"
)

// AutomationStructuredError is the stable run/start error contract exposed by the
// automations service and stored in automation run ErrorJSON when available.
type AutomationStructuredError struct {
	Code       string `json:"code,omitempty"`
	Message    string `json:"message,omitempty"`
	Retryable  bool   `json:"retryable,omitempty"`
	UserAction string `json:"user_action,omitempty"`
	Details    any    `json:"details,omitempty"`
}

type automationDomainError struct {
	cause      error
	automation AutomationStructuredError
}

func (e automationDomainError) Error() string {
	if message := strings.TrimSpace(e.automation.Message); message != "" {
		return message
	}
	if e.cause != nil {
		return e.cause.Error()
	}
	return ""
}

func (e automationDomainError) Unwrap() error { return e.cause }

func automationInvalidArgument(code string, message string, userAction string, details any) error {
	return automationDomainError{
		cause: invalidArgument(message),
		automation: AutomationStructuredError{
			Code:       strings.TrimSpace(code),
			Message:    strings.TrimSpace(message),
			UserAction: strings.TrimSpace(userAction),
			Details:    details,
		},
	}
}

func automationNotFound(code string, message string, userAction string, details any) error {
	return automationDomainError{
		cause: notFound(message),
		automation: AutomationStructuredError{
			Code:       strings.TrimSpace(code),
			Message:    strings.TrimSpace(message),
			UserAction: strings.TrimSpace(userAction),
			Details:    details,
		},
	}
}

func automationForbidden(code string, message string, userAction string, details any) error {
	return automationDomainError{
		cause: forbidden(message),
		automation: AutomationStructuredError{
			Code:       strings.TrimSpace(code),
			Message:    strings.TrimSpace(message),
			UserAction: strings.TrimSpace(userAction),
			Details:    details,
		},
	}
}

func AutomationHTTPStatus(err error) int {
	if err == nil {
		return 0
	}
	return HTTPStatus(err)
}

func AutomationCode(err error) string {
	var automationErr automationDomainError
	if errors.As(err, &automationErr) {
		return strings.TrimSpace(automationErr.automation.Code)
	}
	if errors.Is(err, ErrForbidden) {
		return automationCodePermissionDenied
	}
	return ""
}

func AutomationDetails(err error) any {
	var automationErr automationDomainError
	if errors.As(err, &automationErr) {
		return automationErr.automation.Details
	}
	return nil
}

func AutomationStructured(err error) (*AutomationStructuredError, bool) {
	var automationErr automationDomainError
	if !errors.As(err, &automationErr) {
		return nil, false
	}
	copy := automationErr.automation
	if strings.TrimSpace(copy.Message) == "" && automationErr.cause != nil {
		copy.Message = automationErr.cause.Error()
	}
	return &copy, true
}

func AutomationStructuredErrorJSON(err error) string {
	structured, ok := AutomationStructured(err)
	if !ok {
		return ""
	}
	encoded, marshalErr := json.Marshal(structured)
	if marshalErr != nil {
		return ""
	}
	return string(encoded)
}

// AutomationTriggerContext is the stable trigger provenance contract exposed on
// automation runs and runner acquire payloads.
type AutomationTriggerContext struct {
	TriggerKind          string `json:"trigger_kind,omitempty"`
	RepositoryFullName   string `json:"repository_full_name,omitempty"`
	Ref                  string `json:"ref,omitempty"`
	Branch               string `json:"branch,omitempty"`
	BeforeSHA            string `json:"before_sha,omitempty"`
	AfterSHA             string `json:"after_sha,omitempty"`
	ActorRef             string `json:"actor_ref,omitempty"`
	ActorDisplay         string `json:"actor_display,omitempty"`
	EventID              string `json:"event_id,omitempty"`
	ScheduleDefinitionID string `json:"schedule_definition_id,omitempty"`
	AutomationRunID      string `json:"automation_run_id,omitempty"`
	RawPayloadRef        string `json:"raw_payload_ref,omitempty"`
}

type automationRunMetadataEnvelope struct {
	TriggerContext *AutomationTriggerContext `json:"trigger_context,omitempty"`
}

func buildAutomationRunMetadata(trigger *AutomationTriggerContext) string {
	if trigger == nil || strings.TrimSpace(trigger.TriggerKind) == "" {
		return ""
	}
	encoded, err := json.Marshal(automationRunMetadataEnvelope{TriggerContext: trigger})
	if err != nil {
		return ""
	}
	return string(encoded)
}

func AutomationTriggerContextFromMetadata(raw string, fallbackTriggerKind string) *AutomationTriggerContext {
	trigger := ParseAutomationRunTriggerContext(raw)
	if trigger != nil {
		if strings.TrimSpace(trigger.TriggerKind) == "" {
			trigger.TriggerKind = strings.TrimSpace(fallbackTriggerKind)
		}
		return trigger
	}
	if triggerKind := strings.TrimSpace(fallbackTriggerKind); triggerKind != "" {
		return &AutomationTriggerContext{TriggerKind: triggerKind}
	}
	return nil
}

func ParseAutomationRunTriggerContext(raw string) *AutomationTriggerContext {
	raw = strings.TrimSpace(raw)
	if raw == "" || raw == "{}" {
		return nil
	}
	var envelope automationRunMetadataEnvelope
	if err := json.Unmarshal([]byte(raw), &envelope); err == nil && envelope.TriggerContext != nil {
		if strings.TrimSpace(envelope.TriggerContext.TriggerKind) != "" {
			return envelope.TriggerContext
		}
	}
	var direct AutomationTriggerContext
	if err := json.Unmarshal([]byte(raw), &direct); err == nil && strings.TrimSpace(direct.TriggerKind) != "" {
		return &direct
	}
	return nil
}

func AutomationTriggerContextJSON(raw string, fallbackTriggerKind ...string) string {
	var fallback string
	if len(fallbackTriggerKind) > 0 {
		fallback = fallbackTriggerKind[0]
	}
	trigger := AutomationTriggerContextFromMetadata(raw, fallback)
	if trigger == nil {
		return ""
	}
	encoded, err := json.Marshal(trigger)
	if err != nil {
		return ""
	}
	return string(encoded)
}

func ParseAutomationStructuredError(raw string) *AutomationStructuredError {
	return ParseAutomationRunError(raw)
}

func ParseAutomationRunError(raw string) *AutomationStructuredError {
	raw = strings.TrimSpace(raw)
	if raw == "" || raw == "{}" {
		return nil
	}
	var structured AutomationStructuredError
	if err := json.Unmarshal([]byte(raw), &structured); err == nil {
		if strings.TrimSpace(structured.Code) != "" || strings.TrimSpace(structured.Message) != "" || strings.TrimSpace(structured.UserAction) != "" || structured.Details != nil {
			return &structured
		}
	}
	var legacy map[string]any
	if err := json.Unmarshal([]byte(raw), &legacy); err == nil {
		if message := automationMetadataString(legacy, "message"); message != "" {
			return &AutomationStructuredError{Message: message}
		}
	}
	return &AutomationStructuredError{Message: raw}
}

func automationMetadataString(values map[string]any, key string) string {
	if len(values) == 0 {
		return ""
	}
	value, ok := values[key]
	if !ok {
		return ""
	}
	text, ok := value.(string)
	if !ok {
		return ""
	}
	return strings.TrimSpace(text)
}
