package kubernetes

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"os/exec"
	"strings"

	"github.com/orchard9/rdev/internal/port"
	"github.com/orchard9/rdev/internal/sdlc"
)

// SDLCExecutor runs sdlc CLI commands inside pods via kubectl exec.
type SDLCExecutor struct {
	namespace string
	logger    *slog.Logger
}

// SDLCExecutorConfig configures the SDLC executor.
type SDLCExecutorConfig struct {
	Namespace string
	Logger    *slog.Logger
}

// NewSDLCExecutor creates a new SDLC executor.
func NewSDLCExecutor(cfg SDLCExecutorConfig) *SDLCExecutor {
	logger := cfg.Logger
	if logger == nil {
		logger = slog.Default()
	}
	return &SDLCExecutor{
		namespace: cfg.Namespace,
		logger:    logger.With("component", "sdlc-executor"),
	}
}

// execSDLC runs a sdlc CLI command in the pod and returns stdout bytes.
// All commands include --json for machine-readable output.
func (e *SDLCExecutor) execSDLC(ctx context.Context, podName string, args ...string) ([]byte, error) {
	kubectlArgs := []string{"exec", "-n", e.namespace, podName, "--", "sdlc"}
	kubectlArgs = append(kubectlArgs, args...)
	kubectlArgs = append(kubectlArgs, "--json")

	cmd := exec.CommandContext(ctx, "kubectl", kubectlArgs...)
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	if err := cmd.Run(); err != nil {
		return nil, e.mapExecError(stderr.String(), err)
	}
	return stdout.Bytes(), nil
}

// execSDLCNoOutput runs a sdlc CLI command that produces no meaningful stdout.
func (e *SDLCExecutor) execSDLCNoOutput(ctx context.Context, podName string, args ...string) error {
	_, err := e.execSDLC(ctx, podName, args...)
	return err
}

// mapExecError maps stderr text from the sdlc CLI to sentinel errors.
func (e *SDLCExecutor) mapExecError(stderr string, execErr error) error {
	stderr = strings.TrimSpace(stderr)

	switch {
	case strings.Contains(stderr, "sdlc not initialized"):
		return sdlc.ErrNotInitialized
	case strings.Contains(stderr, "feature not found"):
		return sdlc.ErrFeatureNotFound
	case strings.Contains(stderr, "feature already exists"):
		return sdlc.ErrFeatureExists
	case strings.Contains(stderr, "invalid phase transition"):
		return sdlc.ErrInvalidTransition
	case strings.Contains(stderr, "invalid phase"):
		return sdlc.ErrInvalidPhase
	case strings.Contains(stderr, "task not found"):
		return sdlc.ErrTaskNotFound
	case strings.Contains(stderr, "artifact not found"):
		return sdlc.ErrArtifactNotFound
	case strings.Contains(stderr, "invalid slug"):
		return sdlc.ErrInvalidSlug
	case strings.Contains(stderr, "invalid artifact"):
		return sdlc.ErrInvalidArtifact
	case strings.Contains(stderr, "branch already exists"):
		return sdlc.ErrBranchExists
	case strings.Contains(stderr, "branch not found"):
		return sdlc.ErrBranchNotFound
	case strings.Contains(stderr, "not ready to merge"):
		return sdlc.ErrMergeNotReady
	default:
		if stderr != "" {
			return fmt.Errorf("sdlc exec: %s: %w", stderr, execErr)
		}
		return fmt.Errorf("sdlc exec: %w", execErr)
	}
}

// GetState returns the global SDLC state for a project pod.
func (e *SDLCExecutor) GetState(ctx context.Context, podName string) (*sdlc.State, error) {
	out, err := e.execSDLC(ctx, podName, "state")
	if err != nil {
		return nil, err
	}
	var state sdlc.State
	if err := json.Unmarshal(out, &state); err != nil {
		return nil, fmt.Errorf("parse sdlc state: %w", err)
	}
	return &state, nil
}

// GetNext returns the classifier's recommendation for the next action.
func (e *SDLCExecutor) GetNext(ctx context.Context, podName, feature string) (*sdlc.Classification, error) {
	args := []string{"next"}
	if feature != "" {
		args = append(args, "--feature", feature)
	}
	out, err := e.execSDLC(ctx, podName, args...)
	if err != nil {
		return nil, err
	}
	var cl sdlc.Classification
	if err := json.Unmarshal(out, &cl); err != nil {
		return nil, fmt.Errorf("parse sdlc classification: %w", err)
	}
	return &cl, nil
}

// ListFeatures returns all features in the project.
func (e *SDLCExecutor) ListFeatures(ctx context.Context, podName string) ([]*sdlc.Feature, error) {
	out, err := e.execSDLC(ctx, podName, "feature", "list")
	if err != nil {
		return nil, err
	}
	var features []*sdlc.Feature
	if err := json.Unmarshal(out, &features); err != nil {
		return nil, fmt.Errorf("parse sdlc features: %w", err)
	}
	return features, nil
}

// GetFeature returns a single feature by slug.
func (e *SDLCExecutor) GetFeature(ctx context.Context, podName, slug string) (*sdlc.Feature, error) {
	out, err := e.execSDLC(ctx, podName, "feature", "show", slug)
	if err != nil {
		return nil, err
	}
	var f sdlc.Feature
	if err := json.Unmarshal(out, &f); err != nil {
		return nil, fmt.Errorf("parse sdlc feature: %w", err)
	}
	return &f, nil
}

// CreateFeature creates a new feature with the given slug and title.
func (e *SDLCExecutor) CreateFeature(ctx context.Context, podName, slug, title string) (*sdlc.Feature, error) {
	out, err := e.execSDLC(ctx, podName, "feature", "create", slug, "--title", title)
	if err != nil {
		return nil, err
	}
	var f sdlc.Feature
	if err := json.Unmarshal(out, &f); err != nil {
		return nil, fmt.Errorf("parse sdlc feature: %w", err)
	}
	return &f, nil
}

// TransitionFeature moves a feature to the specified phase.
func (e *SDLCExecutor) TransitionFeature(ctx context.Context, podName, slug string, phase sdlc.FeaturePhase) error {
	return e.execSDLCNoOutput(ctx, podName, "feature", "transition", slug, string(phase))
}

// BlockFeature adds a blocker reason to a feature.
func (e *SDLCExecutor) BlockFeature(ctx context.Context, podName, slug, reason string) error {
	return e.execSDLCNoOutput(ctx, podName, "feature", "block", slug, "--reason", reason)
}

// UnblockFeature removes all blockers from a feature.
func (e *SDLCExecutor) UnblockFeature(ctx context.Context, podName, slug string) error {
	return e.execSDLCNoOutput(ctx, podName, "feature", "unblock", slug)
}

// DeleteFeature removes a feature entirely.
func (e *SDLCExecutor) DeleteFeature(ctx context.Context, podName, slug string) error {
	return e.execSDLCNoOutput(ctx, podName, "feature", "delete", slug, "--force")
}

// GetArtifactStatus returns artifact statuses for a feature.
func (e *SDLCExecutor) GetArtifactStatus(ctx context.Context, podName, slug string) (map[sdlc.ArtifactType]*sdlc.Artifact, error) {
	out, err := e.execSDLC(ctx, podName, "artifact", "status", slug)
	if err != nil {
		return nil, err
	}
	var artifacts map[sdlc.ArtifactType]*sdlc.Artifact
	if err := json.Unmarshal(out, &artifacts); err != nil {
		return nil, fmt.Errorf("parse sdlc artifacts: %w", err)
	}
	return artifacts, nil
}

// ApproveArtifact approves a feature artifact.
func (e *SDLCExecutor) ApproveArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error {
	return e.execSDLCNoOutput(ctx, podName, "artifact", "approve", slug, string(artType))
}

// RejectArtifact rejects a feature artifact.
func (e *SDLCExecutor) RejectArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error {
	return e.execSDLCNoOutput(ctx, podName, "artifact", "reject", slug, string(artType))
}

// PassArtifact marks a feature artifact as passed.
func (e *SDLCExecutor) PassArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error {
	return e.execSDLCNoOutput(ctx, podName, "artifact", "pass", slug, string(artType))
}

// FailArtifact marks a feature artifact as failed.
func (e *SDLCExecutor) FailArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error {
	return e.execSDLCNoOutput(ctx, podName, "artifact", "fail", slug, string(artType))
}

// NeedsFixArtifact marks a feature artifact as needing fixes.
func (e *SDLCExecutor) NeedsFixArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error {
	return e.execSDLCNoOutput(ctx, podName, "artifact", "needs-fix", slug, string(artType))
}

// ListTasks returns all tasks for a feature.
func (e *SDLCExecutor) ListTasks(ctx context.Context, podName, slug string) ([]sdlc.Task, error) {
	out, err := e.execSDLC(ctx, podName, "task", "list", slug)
	if err != nil {
		return nil, err
	}
	var tasks []sdlc.Task
	if err := json.Unmarshal(out, &tasks); err != nil {
		return nil, fmt.Errorf("parse sdlc tasks: %w", err)
	}
	return tasks, nil
}

// AddTask adds a new task to a feature.
func (e *SDLCExecutor) AddTask(ctx context.Context, podName, slug, title string) (*sdlc.Task, error) {
	out, err := e.execSDLC(ctx, podName, "task", "add", slug, "--title", title)
	if err != nil {
		return nil, err
	}
	var t sdlc.Task
	if err := json.Unmarshal(out, &t); err != nil {
		return nil, fmt.Errorf("parse sdlc task: %w", err)
	}
	return &t, nil
}

// StartTask marks a task as in-progress.
func (e *SDLCExecutor) StartTask(ctx context.Context, podName, slug, taskID string) error {
	return e.execSDLCNoOutput(ctx, podName, "task", "start", slug, taskID)
}

// CompleteTask marks a task as complete.
func (e *SDLCExecutor) CompleteTask(ctx context.Context, podName, slug, taskID string) error {
	return e.execSDLCNoOutput(ctx, podName, "task", "complete", slug, taskID)
}

// BlockTask marks a task as blocked.
func (e *SDLCExecutor) BlockTask(ctx context.Context, podName, slug, taskID string) error {
	return e.execSDLCNoOutput(ctx, podName, "task", "block", slug, taskID)
}

// QueryBlocked returns all blocked features.
func (e *SDLCExecutor) QueryBlocked(ctx context.Context, podName string) ([]port.BlockedInfo, error) {
	out, err := e.execSDLC(ctx, podName, "query", "blocked")
	if err != nil {
		return nil, err
	}
	var blocked []port.BlockedInfo
	if err := json.Unmarshal(out, &blocked); err != nil {
		return nil, fmt.Errorf("parse sdlc blocked query: %w", err)
	}
	return blocked, nil
}

// QueryReady returns features ready for work.
func (e *SDLCExecutor) QueryReady(ctx context.Context, podName string) ([]port.ReadyInfo, error) {
	out, err := e.execSDLC(ctx, podName, "query", "ready")
	if err != nil {
		return nil, err
	}
	var ready []port.ReadyInfo
	if err := json.Unmarshal(out, &ready); err != nil {
		return nil, fmt.Errorf("parse sdlc ready query: %w", err)
	}
	return ready, nil
}

// QueryNeedsApproval returns features awaiting approval.
func (e *SDLCExecutor) QueryNeedsApproval(ctx context.Context, podName string) ([]port.ApprovalInfo, error) {
	out, err := e.execSDLC(ctx, podName, "query", "needs-approval")
	if err != nil {
		return nil, err
	}
	var pending []port.ApprovalInfo
	if err := json.Unmarshal(out, &pending); err != nil {
		return nil, fmt.Errorf("parse sdlc approval query: %w", err)
	}
	return pending, nil
}

// CreateBranch creates a feature branch and its manifest.
func (e *SDLCExecutor) CreateBranch(ctx context.Context, podName, slug string) (*sdlc.BranchManifest, error) {
	out, err := e.execSDLC(ctx, podName, "branch", "create", slug)
	if err != nil {
		return nil, err
	}
	var manifest sdlc.BranchManifest
	if err := json.Unmarshal(out, &manifest); err != nil {
		return nil, fmt.Errorf("parse sdlc branch manifest: %w", err)
	}
	return &manifest, nil
}

// GetBranchStatus returns the full branch status including checklist.
func (e *SDLCExecutor) GetBranchStatus(ctx context.Context, podName, slug string) (*port.BranchStatus, error) {
	out, err := e.execSDLC(ctx, podName, "branch", "status", slug)
	if err != nil {
		return nil, err
	}
	var result port.BranchStatus
	if err := json.Unmarshal(out, &result); err != nil {
		return nil, fmt.Errorf("parse sdlc branch status: %w", err)
	}
	return &result, nil
}

// SyncBranch syncs a feature branch with its base branch.
func (e *SDLCExecutor) SyncBranch(ctx context.Context, podName, slug string) error {
	return e.execSDLCNoOutput(ctx, podName, "branch", "sync", slug)
}

// MergeFeature merges a feature branch after all gates pass.
func (e *SDLCExecutor) MergeFeature(ctx context.Context, podName, slug, strategy string) error {
	args := []string{"merge", slug}
	if strategy != "" {
		args = append(args, "--strategy", strategy)
	}
	return e.execSDLCNoOutput(ctx, podName, args...)
}

// ArchiveFeature archives a released feature.
func (e *SDLCExecutor) ArchiveFeature(ctx context.Context, podName, slug string) error {
	return e.execSDLCNoOutput(ctx, podName, "archive", slug)
}

// Compile-time interface check.
var _ port.SDLCExecutor = (*SDLCExecutor)(nil)
