package runtime

import (
	"context"
	"errors"
	"io"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/automation/dsl"
)

// JobExecutor executes one validated server-managed automation job.
type JobExecutor interface {
	ExecuteJob(ctx context.Context, req ExecuteJobRequest) (ExecuteJobResult, error)
}

// ExecuteJobRequest carries the execution-only state for one automation job.
type ExecuteJobRequest struct {
	RunnerID        string
	Job             JobExecutionIdentity
	Spec            dsl.JobSpec
	WorkDir         string
	Env             []string
	StepEnv         [][]string
	Stdout          io.Writer
	Stderr          io.Writer
	Now             func() time.Time
	UsesStepHandler UsesStepHandler
}

// UsesStepHandler executes one validated non-shell uses step while preserving
// executor-controlled job ordering.
type UsesStepHandler func(ctx context.Context, req UsesStepRequest) (int, error)

// UsesStepRequest carries one uses step delegated back to the server runner.
type UsesStepRequest struct {
	Step  dsl.StepSpec
	Index int
}

// JobExecutionIdentity carries stable job identifiers used for executor-side
// labels and environment, without exposing persistence models.
type JobExecutionIdentity struct {
	QueueJobID  int64
	WorkspaceID string
	RunID       string
	JobRunID    string
	JobID       string
}

// ExecuteJobResult records execution output that is persisted into the job run.
type ExecuteJobResult struct {
	Steps []JobStepResult
}

// JobStepResult records the durable result for one executed automation step.
type JobStepResult struct {
	ID          string `json:"id,omitempty"`
	Name        string `json:"name,omitempty"`
	Index       int    `json:"index"`
	ExitCode    int    `json:"exit_code"`
	StartedAt   string `json:"started_at"`
	CompletedAt string `json:"completed_at"`
}

type ExecutorFailureKind string

const (
	ExecutorFailureDockerUnavailable ExecutorFailureKind = "docker_unavailable"
	ExecutorFailureImageMissing      ExecutorFailureKind = "image_missing"
	ExecutorFailureContainerCleanup  ExecutorFailureKind = "container_cleanup"
)

// ExecutorError wraps executor failures with a stable low-cardinality kind.
type ExecutorError struct {
	Kind ExecutorFailureKind
	Err  error
}

func (e ExecutorError) Error() string {
	return e.Err.Error()
}

func (e ExecutorError) Unwrap() error {
	return e.Err
}

// ExecutorFailureKindFor returns the stable kind for an executor error chain.
func ExecutorFailureKindFor(err error) ExecutorFailureKind {
	if err == nil {
		return ""
	}
	if kind := primaryExecutorFailureKind(err); kind != "" {
		return kind
	}
	if executorCleanupFailure(err) != nil {
		return ExecutorFailureContainerCleanup
	}
	return ""
}

func primaryExecutorFailureKind(err error) ExecutorFailureKind {
	switch e := err.(type) {
	case nil:
		return ""
	case executorCleanupError:
		return ""
	case *executorCleanupError:
		return ""
	case ExecutorError:
		return e.Kind
	case *ExecutorError:
		if e == nil {
			return ""
		}
		return e.Kind
	case interface{ Unwrap() []error }:
		for _, child := range e.Unwrap() {
			if kind := primaryExecutorFailureKind(child); kind != "" {
				return kind
			}
		}
	case interface{ Unwrap() error }:
		return primaryExecutorFailureKind(e.Unwrap())
	}
	return ""
}

type executorCleanupError struct {
	err error
}

func (e executorCleanupError) Error() string {
	return e.err.Error()
}

func (e executorCleanupError) Unwrap() error {
	return e.err
}

func markExecutorCleanupError(err error) error {
	if err == nil {
		return nil
	}
	return executorCleanupError{err: err}
}

func executorCleanupFailure(err error) error {
	var cleanupErr executorCleanupError
	if errors.As(err, &cleanupErr) {
		return cleanupErr.err
	}
	return nil
}
