package worker

import (
	"context"
	"fmt"
	"strings"
	"time"

	claudeboxclient "github.com/orchard9/rdev/internal/adapter/claudebox"
	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/logging"
	"github.com/orchard9/rdev/internal/port"
)

// HTTPBuildExecutor handles WorkTaskTypeBuild tasks using HTTP calls to the
// local claudebox sidecar instead of kubectl exec.
type HTTPBuildExecutor struct {
	client  *claudeboxclient.Client
	streams port.StreamPublisher
	workDir string
}

// HTTPBuildExecutorConfig holds configuration for the HTTP build executor.
type HTTPBuildExecutorConfig struct {
	// ClaudeboxClient is the HTTP client for the claudebox sidecar.
	ClaudeboxClient *claudeboxclient.Client

	// Streams is the SSE stream publisher for real-time events.
	Streams port.StreamPublisher

	// WorkDir is the default working directory in the container.
	WorkDir string
}

// NewHTTPBuildExecutor creates a new HTTP-based build executor.
func NewHTTPBuildExecutor(cfg HTTPBuildExecutorConfig) *HTTPBuildExecutor {
	if cfg.WorkDir == "" {
		cfg.WorkDir = "/workspace"
	}
	return &HTTPBuildExecutor{
		client:  cfg.ClaudeboxClient,
		streams: cfg.Streams,
		workDir: cfg.WorkDir,
	}
}

// Execute runs a build task using the claudebox sidecar HTTP API.
func (e *HTTPBuildExecutor) Execute(ctx context.Context, task *domain.WorkTask) *domain.BuildResult {
	log := logging.FromContext(ctx).WithWorker("http-build-executor")
	start := time.Now()
	streamID := task.ID

	// Publish started event
	e.publishEvent(streamID, BuildEventStarted, map[string]any{
		"task_id":    task.ID,
		"project_id": task.ProjectID,
		"started_at": start.Format(time.RFC3339),
	})

	// Parse build spec
	spec, err := e.parseSpec(task.Spec)
	if err != nil {
		e.publishEvent(streamID, BuildEventFailed, map[string]any{
			"task_id": task.ID,
			"error":   fmt.Sprintf("invalid build spec: %v", err),
		})
		return &domain.BuildResult{
			Success:    false,
			Error:      fmt.Sprintf("invalid build spec: %v", err),
			DurationMs: time.Since(start).Milliseconds(),
		}
	}

	// Clone or update repository if git operations are needed
	if (spec.AutoCommit || spec.AutoPush) && e.client != nil {
		if spec.GitCloneURL == "" {
			e.publishEvent(streamID, BuildEventFailed, map[string]any{
				"task_id": task.ID,
				"error":   "git_clone_url is required when auto_commit or auto_push is enabled",
			})
			return &domain.BuildResult{
				Success:    false,
				Error:      "git_clone_url is required when auto_commit or auto_push is enabled",
				DurationMs: time.Since(start).Milliseconds(),
			}
		}

		log.Info("cloning repository via HTTP", "task_id", task.ID)

		cloneResp, err := e.client.GitClone(ctx, spec.GitCloneURL, e.workDir)
		if err != nil {
			e.publishEvent(streamID, BuildEventFailed, map[string]any{
				"task_id": task.ID,
				"error":   fmt.Sprintf("git clone failed: %v", err),
			})
			return &domain.BuildResult{
				Success:    false,
				Error:      fmt.Sprintf("git clone failed: %v", err),
				DurationMs: time.Since(start).Milliseconds(),
			}
		}

		if !cloneResp.Success {
			e.publishEvent(streamID, BuildEventFailed, map[string]any{
				"task_id": task.ID,
				"error":   fmt.Sprintf("git clone failed: %s", cloneResp.Error),
			})
			return &domain.BuildResult{
				Success:    false,
				Error:      fmt.Sprintf("git clone failed: %s", cloneResp.Error),
				DurationMs: time.Since(start).Milliseconds(),
			}
		}

		if cloneResp.Cloned {
			e.publishEvent(streamID, BuildEventOutput, map[string]any{
				"content": fmt.Sprintf("Cloned repository to %s", e.workDir),
			})
		}
	}

	// Execute Claude Code via HTTP
	log.Info("executing Claude Code via HTTP", "task_id", task.ID, "prompt_len", len(spec.Prompt))

	var output strings.Builder
	const maxOutputSize = 1 << 20 // 1MB

	// Use streaming execution
	execErr := e.client.ExecuteStream(ctx, &claudeboxclient.ExecuteRequest{
		Prompt:     spec.Prompt,
		WorkingDir: e.workDir,
		Timeout:    600, // 10 minutes
	}, func(evt claudeboxclient.StreamEvent) {
		// Map event types
		eventType := BuildEventOutput
		switch evt.Type {
		case "tool_use":
			eventType = BuildEventToolUse
		case "tool_result":
			eventType = BuildEventToolResult
		case "error":
			eventType = BuildEventError
		}

		e.publishEvent(streamID, eventType, map[string]any{
			"content":   evt.Content,
			"stream":    evt.Stream,
			"tool_name": evt.ToolName,
		})

		// Buffer output
		if evt.Type == "output" || evt.Type == "error" {
			if output.Len() >= maxOutputSize {
				return
			}
			if output.Len() > 0 {
				output.WriteString("\n")
			}
			remaining := maxOutputSize - output.Len()
			if len(evt.Content) > remaining {
				output.WriteString(evt.Content[:remaining])
				output.WriteString("\n... [output truncated at 1MB]")
			} else {
				output.WriteString(evt.Content)
			}
		}
	})

	if execErr != nil {
		e.publishEvent(streamID, BuildEventFailed, map[string]any{
			"task_id":     task.ID,
			"error":       fmt.Sprintf("agent execution failed: %v", execErr),
			"duration_ms": time.Since(start).Milliseconds(),
		})
		e.closeStream(ctx, streamID)
		return &domain.BuildResult{
			Success:    false,
			Error:      fmt.Sprintf("agent execution failed: %v", execErr),
			Output:     output.String(),
			DurationMs: time.Since(start).Milliseconds(),
		}
	}

	result := &domain.BuildResult{
		Success:    true,
		Output:     output.String(),
		DurationMs: time.Since(start).Milliseconds(),
		Artifacts:  make(map[string]string),
	}

	// Include SDLC context in artifacts for callback routing
	if spec.SDLCContext != nil {
		if spec.SDLCContext.Feature != "" {
			result.Artifacts["sdlc_feature"] = spec.SDLCContext.Feature
		}
		if spec.SDLCContext.ArtifactType != "" {
			result.Artifacts["sdlc_artifact_type"] = spec.SDLCContext.ArtifactType
		}
		if spec.SDLCContext.TaskID != "" {
			result.Artifacts["sdlc_task_id"] = spec.SDLCContext.TaskID
		}
	}

	// Post-build git operations: commit and push changes
	if result.Success && spec.AutoCommit && e.client != nil {
		commitMsg := fmt.Sprintf("build: %s", truncate(spec.Prompt, 72))
		gitResp, err := e.client.GitCommitAndPush(ctx, commitMsg, spec.AutoPush, e.workDir)

		if err != nil {
			log.Warn("post-build git operations failed", "task_id", task.ID, "error", err)
			result.Success = false
			result.Error = fmt.Sprintf("build succeeded but git operations failed: %v", err)
		} else if !gitResp.Success {
			log.Warn("post-build git operations failed", "task_id", task.ID, "error", gitResp.Error)
			result.Success = false
			result.Error = fmt.Sprintf("build succeeded but git operations failed: %s", gitResp.Error)
		} else if gitResp.HasChanges {
			result.CommitSHA = gitResp.CommitSHA
			result.FilesChanged = gitResp.FilesChanged
			log.Info("post-build git operations completed",
				"task_id", task.ID,
				"commit", gitResp.CommitSHA,
				"files", len(gitResp.FilesChanged),
				"pushed", gitResp.Pushed,
			)
		} else {
			log.Info("no changes to commit after build", "task_id", task.ID)
		}
	}

	// Publish completion event
	if result.Success {
		e.publishEvent(streamID, BuildEventCompleted, map[string]any{
			"task_id":       task.ID,
			"success":       true,
			"commit_sha":    result.CommitSHA,
			"files_changed": result.FilesChanged,
			"duration_ms":   result.DurationMs,
		})
	} else {
		e.publishEvent(streamID, BuildEventFailed, map[string]any{
			"task_id":     task.ID,
			"error":       result.Error,
			"duration_ms": result.DurationMs,
		})
	}
	e.closeStream(ctx, streamID)

	return result
}

// publishEvent publishes an event to the SSE stream.
func (e *HTTPBuildExecutor) publishEvent(streamID, eventType string, data map[string]any) {
	if e.streams == nil {
		return
	}
	e.streams.Publish(streamID, port.StreamEvent{
		Type: eventType,
		Data: data,
	})
}

// closeStream closes the stream after a delay.
func (e *HTTPBuildExecutor) closeStream(ctx context.Context, streamID string) {
	if e.streams == nil {
		return
	}
	go func() {
		select {
		case <-ctx.Done():
			e.streams.Close(streamID)
		case <-time.After(streamCloseDelay):
			e.streams.Close(streamID)
		}
	}()
}

// httpBuildSpec holds typed fields extracted from the task spec map.
type httpBuildSpec struct {
	Prompt      string
	AutoCommit  bool
	AutoPush    bool
	GitCloneURL string
	SDLCContext *sdlcContext
}

// parseSpec extracts typed BuildSpec fields from the generic map.
func (e *HTTPBuildExecutor) parseSpec(spec map[string]any) (*httpBuildSpec, error) {
	prompt, _ := spec["prompt"].(string)
	if prompt == "" {
		return nil, fmt.Errorf("prompt is required")
	}

	autoCommit, _ := spec["auto_commit"].(bool)
	autoPush, _ := spec["auto_push"].(bool)
	gitCloneURL, _ := spec["git_clone_url"].(string)

	parsed := &httpBuildSpec{
		Prompt:      prompt,
		AutoCommit:  autoCommit,
		AutoPush:    autoPush,
		GitCloneURL: gitCloneURL,
	}

	// Extract SDLC context if present
	if sdlcCtx, ok := spec["sdlc_context"].(map[string]any); ok {
		parsed.SDLCContext = &sdlcContext{
			Feature:      stringFromMap(sdlcCtx, "feature"),
			ArtifactType: stringFromMap(sdlcCtx, "artifact_type"),
			TaskID:       stringFromMap(sdlcCtx, "task_id"),
		}
	}

	return parsed, nil
}
