package worker

import (
	"encoding/json"
	"time"

	agentdomain "github.com/codex-k8s/kodex/libs/go/domain/agent"
	rundomain "github.com/codex-k8s/kodex/libs/go/domain/run"
)

const payloadMarshalFailedError = "payload_marshal_failed"

// namespaceCleanupSkipReason keeps legacy reason field for backwards-compatible payload decoding.
type namespaceCleanupSkipReason string

// runFailureReason keeps normalized worker-side failure reasons in flow-event payloads.
type runFailureReason string

const (
	runFailureReasonKubernetesJobFailed    runFailureReason = "kubernetes job failed"
	runFailureReasonKubernetesJobNotFound  runFailureReason = "kubernetes job not found"
	runFailureReasonNamespacePrepareFailed runFailureReason = "namespace_prepare_failed"
	runFailureReasonRuntimeDeployFailed    runFailureReason = "runtime_deploy_failed"
	runFailureReasonRuntimeDeployCanceled  runFailureReason = "runtime_deploy_canceled"
	runFailureReasonMCPTokenIssueFailed    runFailureReason = "mcp_token_issue_failed"
	runFailureReasonAgentContextResolve    runFailureReason = "agent_context_resolve_failed"
	runFailureReasonPreconditionFailed     runFailureReason = "failed_precondition"
)

const (
	namespaceCleanupReasonTTLExpired          namespaceCleanupSkipReason = "ttl_expired"
	namespaceCleanupReasonMissingRunIDLabel   namespaceCleanupSkipReason = "missing_run_id_label"
	namespaceCleanupReasonActiveRunInDB       namespaceCleanupSkipReason = "active_run_in_db"
	namespaceCleanupReasonActiveWorkloads     namespaceCleanupSkipReason = "active_workloads_present"
	namespaceCleanupReasonNoEffectiveLease    namespaceCleanupSkipReason = "missing_effective_lease"
	namespaceCleanupReasonDeleteFailed        namespaceCleanupSkipReason = "delete_failed"
	namespaceCleanupReasonInspectFailed       namespaceCleanupSkipReason = "workload_inspection_failed"
	namespaceCleanupReasonCommentUpdateFailed namespaceCleanupSkipReason = "status_comment_update_failed"
	namespaceCleanupReasonEventsInsertFailed  namespaceCleanupSkipReason = "audit_insert_failed"
	namespaceCleanupReasonDisabledByConfig    namespaceCleanupSkipReason = "cleanup_disabled"
	namespaceCleanupReasonRunLookupFailed     namespaceCleanupSkipReason = "run_lookup_failed"
	namespaceCleanupReasonAlreadyDeleted      namespaceCleanupSkipReason = "already_deleted"
	namespaceCleanupReasonNoExpiredNamespaces namespaceCleanupSkipReason = "no_expired_namespaces"
)

// runStartedEventPayload defines payload shape for run.started flow events.
type runStartedEventPayload struct {
	RunID                string                  `json:"run_id"`
	ProjectID            string                  `json:"project_id"`
	SlotNo               int                     `json:"slot_no"`
	JobName              string                  `json:"job_name"`
	JobNamespace         string                  `json:"job_namespace"`
	RuntimeMode          agentdomain.RuntimeMode `json:"runtime_mode"`
	RepositoryFullName   string                  `json:"repository_full_name,omitempty"`
	AgentKey             string                  `json:"agent_key,omitempty"`
	IssueNumber          int64                   `json:"issue_number,omitempty"`
	TriggerKind          string                  `json:"trigger_kind,omitempty"`
	TriggerLabel         string                  `json:"trigger_label,omitempty"`
	DiscussionMode       bool                    `json:"discussion_mode,omitempty"`
	JobImage             string                  `json:"job_image,omitempty"`
	Model                string                  `json:"model,omitempty"`
	ModelSource          string                  `json:"model_source,omitempty"`
	ReasoningEffort      string                  `json:"reasoning_effort,omitempty"`
	ReasoningSource      string                  `json:"reasoning_source,omitempty"`
	PromptTemplateKind   string                  `json:"prompt_template_kind,omitempty"`
	PromptTemplateSource string                  `json:"prompt_template_source,omitempty"`
	PromptTemplateLocale string                  `json:"prompt_template_locale,omitempty"`
	BaseBranch           string                  `json:"base_branch,omitempty"`
}

// runProfileResolvedEventPayload defines payload shape for run.profile.resolved flow events.
type runProfileResolvedEventPayload struct {
	RunID              string `json:"run_id"`
	ProjectID          string `json:"project_id"`
	RepositoryFullName string `json:"repository_full_name,omitempty"`
	IssueNumber        int64  `json:"issue_number,omitempty"`
	PullRequestNumber  int    `json:"pull_request_number,omitempty"`
	TriggerKind        string `json:"trigger_kind,omitempty"`
	DiscussionMode     bool   `json:"discussion_mode,omitempty"`
	Model              string `json:"model,omitempty"`
	ModelSource        string `json:"model_source,omitempty"`
	ReasoningEffort    string `json:"reasoning_effort,omitempty"`
	ReasoningSource    string `json:"reasoning_source,omitempty"`
}

// runJobImageResolvedEventPayload defines payload shape for run.job.image.resolved flow events.
type runJobImageResolvedEventPayload struct {
	RunID             string `json:"run_id"`
	ProjectID         string `json:"project_id"`
	SelectedImage     string `json:"selected_image,omitempty"`
	SelectedSource    string `json:"selected_source,omitempty"`
	PrimaryImage      string `json:"primary_image,omitempty"`
	FallbackImage     string `json:"fallback_image,omitempty"`
	PrimaryAvailable  bool   `json:"primary_available,omitempty"`
	FallbackAvailable bool   `json:"fallback_available,omitempty"`
	CheckError        string `json:"check_error,omitempty"`
}

// runFinishedEventPayload defines payload shape for run finish flow events.
type runFinishedEventPayload struct {
	RunID        string                  `json:"run_id"`
	ProjectID    string                  `json:"project_id"`
	Status       rundomain.Status        `json:"status"`
	JobName      string                  `json:"job_name"`
	JobNamespace string                  `json:"job_namespace"`
	RuntimeMode  agentdomain.RuntimeMode `json:"runtime_mode"`
	Namespace    string                  `json:"namespace,omitempty"`
	Error        string                  `json:"error,omitempty"`
	Reason       runFailureReason        `json:"reason,omitempty"`
}

// runFinishedEventExtra carries optional failure details for run finish payloads.
type runFinishedEventExtra struct {
	Error  string
	Reason runFailureReason
}

// workerHeartbeatMissedEventPayload defines payload shape for stale worker heartbeat events.
type workerHeartbeatMissedEventPayload struct {
	RunID              string `json:"run_id"`
	ProjectID          string `json:"project_id"`
	WorkerID           string `json:"worker_id"`
	WorkerStatus       string `json:"worker_status,omitempty"`
	WorkerHeartbeatAt  string `json:"worker_heartbeat_at,omitempty"`
	WorkerExpiresAt    string `json:"worker_expires_at,omitempty"`
	PreviousLeaseUntil string `json:"previous_lease_until,omitempty"`
}

// runLeaseStaleEventPayload defines payload shape for stale lease detection/release events.
type runLeaseStaleEventPayload struct {
	RunID              string `json:"run_id"`
	ProjectID          string `json:"project_id"`
	PreviousLeaseOwner string `json:"previous_lease_owner"`
	PreviousLeaseUntil string `json:"previous_lease_until,omitempty"`
	WorkerStatus       string `json:"worker_status,omitempty"`
	WorkerHeartbeatAt  string `json:"worker_heartbeat_at,omitempty"`
	WorkerExpiresAt    string `json:"worker_expires_at,omitempty"`
}

// runLeaseRecoveredEventPayload defines payload shape for reclaimed stale lease events.
type runLeaseRecoveredEventPayload struct {
	RunID          string `json:"run_id"`
	ProjectID      string `json:"project_id"`
	WorkerID       string `json:"worker_id"`
	RuntimeMode    string `json:"runtime_mode,omitempty"`
	Namespace      string `json:"namespace,omitempty"`
	TriggerKind    string `json:"trigger_kind,omitempty"`
	DiscussionMode bool   `json:"discussion_mode,omitempty"`
}

// namespaceLifecycleEventPayload defines payload shape for namespace lifecycle flow events.
type namespaceLifecycleEventPayload struct {
	RunID                   string                     `json:"run_id"`
	ProjectID               string                     `json:"project_id"`
	RuntimeMode             agentdomain.RuntimeMode    `json:"runtime_mode"`
	Namespace               string                     `json:"namespace"`
	Error                   string                     `json:"error,omitempty"`
	Reason                  namespaceCleanupSkipReason `json:"reason,omitempty"`
	GuardrailDetails        []string                   `json:"guardrail_details,omitempty"`
	CleanupCommand          string                     `json:"cleanup_command,omitempty"`
	NamespaceLeaseTTL       string                     `json:"namespace_lease_ttl,omitempty"`
	NamespaceLeaseExpiresAt string                     `json:"namespace_lease_expires_at,omitempty"`
	NamespaceReused         bool                       `json:"namespace_reused,omitempty"`
}

// namespaceLifecycleEventExtra carries optional namespace lifecycle diagnostics.
type namespaceLifecycleEventExtra struct {
	Error                   string
	Reason                  namespaceCleanupSkipReason
	GuardrailDetails        []string
	CleanupCommand          string
	NamespaceLeaseTTL       time.Duration
	NamespaceLeaseExpiresAt time.Time
	NamespaceReused         bool
}

// runtimeReuseEventPayload defines payload shape for fast-path/fallback runtime reuse evidence.
type runtimeReuseEventPayload struct {
	RunID             string `json:"run_id"`
	ProjectID         string `json:"project_id"`
	Namespace         string `json:"namespace"`
	IssueNumber       int64  `json:"issue_number,omitempty"`
	AgentKey          string `json:"agent_key,omitempty"`
	TriggerKind       string `json:"trigger_kind,omitempty"`
	Reason            string `json:"reason,omitempty"`
	EffectiveBuildRef string `json:"effective_build_ref,omitempty"`
	FingerprintHash   string `json:"fingerprint_hash,omitempty"`
}

// payloadMarshalError is fallback payload shape used when JSON serialization unexpectedly fails.
type payloadMarshalError struct {
	Error string `json:"error"`
}

// encodeRunStartedEventPayload serializes run.started payload with safe fallback JSON.
func encodeRunStartedEventPayload(payload runStartedEventPayload) json.RawMessage {
	bytes, err := json.Marshal(payload)
	return marshalPayload(bytes, err)
}

// encodeRunProfileResolvedEventPayload serializes run.profile.resolved payload with safe fallback JSON.
func encodeRunProfileResolvedEventPayload(payload runProfileResolvedEventPayload) json.RawMessage {
	bytes, err := json.Marshal(payload)
	return marshalPayload(bytes, err)
}

// encodeRunJobImageResolvedEventPayload serializes run.job.image.resolved payload with safe fallback JSON.
func encodeRunJobImageResolvedEventPayload(payload runJobImageResolvedEventPayload) json.RawMessage {
	bytes, err := json.Marshal(payload)
	return marshalPayload(bytes, err)
}

// encodeRunFinishedEventPayload serializes run finish payload with safe fallback JSON.
func encodeRunFinishedEventPayload(payload runFinishedEventPayload) json.RawMessage {
	bytes, err := json.Marshal(payload)
	return marshalPayload(bytes, err)
}

// encodeWorkerHeartbeatMissedEventPayload serializes stale worker heartbeat payload with safe fallback JSON.
func encodeWorkerHeartbeatMissedEventPayload(payload workerHeartbeatMissedEventPayload) json.RawMessage {
	bytes, err := json.Marshal(payload)
	return marshalPayload(bytes, err)
}

// encodeRunLeaseStaleEventPayload serializes stale lease payload with safe fallback JSON.
func encodeRunLeaseStaleEventPayload(payload runLeaseStaleEventPayload) json.RawMessage {
	bytes, err := json.Marshal(payload)
	return marshalPayload(bytes, err)
}

// encodeRunLeaseRecoveredEventPayload serializes reclaimed stale lease payload with safe fallback JSON.
func encodeRunLeaseRecoveredEventPayload(payload runLeaseRecoveredEventPayload) json.RawMessage {
	bytes, err := json.Marshal(payload)
	return marshalPayload(bytes, err)
}

// encodeNamespaceLifecycleEventPayload serializes namespace lifecycle payload with safe fallback JSON.
func encodeNamespaceLifecycleEventPayload(payload namespaceLifecycleEventPayload) json.RawMessage {
	bytes, err := json.Marshal(payload)
	return marshalPayload(bytes, err)
}

// encodeRuntimeReuseEventPayload serializes runtime reuse evidence payload with safe fallback JSON.
func encodeRuntimeReuseEventPayload(payload runtimeReuseEventPayload) json.RawMessage {
	bytes, err := json.Marshal(payload)
	return marshalPayload(bytes, err)
}

// marshalPayload centralizes safe JSON fallback to keep event publishing non-blocking on marshal errors.
func marshalPayload(bytes []byte, err error) json.RawMessage {
	if err == nil {
		return json.RawMessage(bytes)
	}
	fallback, fallbackErr := json.Marshal(payloadMarshalError{Error: payloadMarshalFailedError})
	if fallbackErr != nil {
		return json.RawMessage(`{"error":"payload_marshal_failed"}`)
	}
	return json.RawMessage(fallback)
}
