package claudebox

import (
	"bufio"
	"context"
	"fmt"
	"io"
	"os/exec"
	"strings"
	"sync"
	"time"
)

// Default allowed tools for Claude Code execution.
var defaultAllowedTools = []string{
	"Bash", "Edit", "Write", "Read", "Glob", "Grep", "Task", "WebFetch", "WebSearch",
}

// Executor runs Claude Code locally in the container.
type Executor struct {
	workDir string
}

// NewExecutor creates a new local executor.
func NewExecutor(workDir string) *Executor {
	return &Executor{
		workDir: workDir,
	}
}

// ExecuteResult contains the result of a Claude Code execution.
type ExecuteResult struct {
	Success     bool
	Output      string
	ExitCode    int
	DurationMs  int64
	Error       error
	SessionID   string
	FinalOutput string
}

// Execute runs Claude Code and returns the complete result.
func (e *Executor) Execute(ctx context.Context, req *ExecuteRequest) *ExecuteResult {
	var output strings.Builder
	start := time.Now()

	result := &ExecuteResult{}

	// Apply timeout if specified
	if req.Timeout > 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, time.Duration(req.Timeout)*time.Second)
		defer cancel()
	}

	// Build command args
	args := e.buildArgs(req)

	// Execute claude command
	cmd := exec.CommandContext(ctx, "claude", args...)

	// Get working directory
	workDir := req.WorkingDir
	if workDir == "" {
		workDir = e.workDir
	}
	cmd.Dir = workDir

	// Capture output
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		result.Error = fmt.Errorf("stdout pipe: %w", err)
		result.DurationMs = time.Since(start).Milliseconds()
		return result
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		result.Error = fmt.Errorf("stderr pipe: %w", err)
		result.DurationMs = time.Since(start).Milliseconds()
		return result
	}

	if err := cmd.Start(); err != nil {
		result.Error = fmt.Errorf("start: %w", err)
		result.DurationMs = time.Since(start).Milliseconds()
		return result
	}

	// Read output
	var wg sync.WaitGroup
	wg.Add(2)

	go func() {
		defer wg.Done()
		scanner := bufio.NewScanner(stdout)
		buf := make([]byte, 0, 64*1024)
		scanner.Buffer(buf, 1024*1024)
		for scanner.Scan() {
			output.WriteString(scanner.Text())
			output.WriteString("\n")
		}
	}()

	var stderrOutput strings.Builder
	go func() {
		defer wg.Done()
		scanner := bufio.NewScanner(stderr)
		buf := make([]byte, 0, 64*1024)
		scanner.Buffer(buf, 1024*1024)
		for scanner.Scan() {
			stderrOutput.WriteString(scanner.Text())
			stderrOutput.WriteString("\n")
		}
	}()

	wg.Wait()
	cmdErr := cmd.Wait()

	result.DurationMs = time.Since(start).Milliseconds()
	result.Output = output.String()
	result.FinalOutput = output.String()

	if cmdErr != nil {
		if exitErr, ok := cmdErr.(*exec.ExitError); ok {
			result.ExitCode = exitErr.ExitCode()
		} else {
			result.ExitCode = 1
			result.Error = cmdErr
		}

		// Append stderr to error message
		if stderrOutput.Len() > 0 {
			if result.Error != nil {
				result.Error = fmt.Errorf("%w\nstderr: %s", result.Error, stderrOutput.String())
			} else {
				result.Error = fmt.Errorf("stderr: %s", stderrOutput.String())
			}
		}
	} else {
		result.Success = true
	}

	return result
}

// StreamEventHandler is called for each event during streaming execution.
type StreamEventHandler func(StreamEvent)

// ExecuteStream runs Claude Code and streams events to the handler.
func (e *Executor) ExecuteStream(ctx context.Context, req *ExecuteRequest, handler StreamEventHandler) *ExecuteResult {
	start := time.Now()
	result := &ExecuteResult{}

	// Apply timeout if specified
	if req.Timeout > 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, time.Duration(req.Timeout)*time.Second)
		defer cancel()
	}

	// Build command args with stream-json output
	args := e.buildStreamArgs(req)

	cmd := exec.CommandContext(ctx, "claude", args...)

	workDir := req.WorkingDir
	if workDir == "" {
		workDir = e.workDir
	}
	cmd.Dir = workDir

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		result.Error = fmt.Errorf("stdout pipe: %w", err)
		result.DurationMs = time.Since(start).Milliseconds()
		return result
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		result.Error = fmt.Errorf("stderr pipe: %w", err)
		result.DurationMs = time.Since(start).Milliseconds()
		return result
	}

	if err := cmd.Start(); err != nil {
		result.Error = fmt.Errorf("start: %w", err)
		result.DurationMs = time.Since(start).Milliseconds()
		return result
	}

	// Emit started event
	handler(StreamEvent{
		Type:      "started",
		Timestamp: time.Now().UTC().Format(time.RFC3339),
	})

	// Stream output
	var wg sync.WaitGroup
	var output strings.Builder

	wg.Add(2)

	go func() {
		defer wg.Done()
		e.streamOutput(stdout, "stdout", handler, &output)
	}()

	go func() {
		defer wg.Done()
		e.streamStderr(stderr, handler)
	}()

	wg.Wait()
	cmdErr := cmd.Wait()

	result.DurationMs = time.Since(start).Milliseconds()
	result.Output = output.String()
	result.FinalOutput = output.String()

	if cmdErr != nil {
		if exitErr, ok := cmdErr.(*exec.ExitError); ok {
			result.ExitCode = exitErr.ExitCode()
		} else {
			result.ExitCode = 1
			result.Error = cmdErr
		}

		handler(StreamEvent{
			Type:      "failed",
			Content:   cmdErr.Error(),
			Timestamp: time.Now().UTC().Format(time.RFC3339),
		})
	} else {
		result.Success = true
		handler(StreamEvent{
			Type:      "completed",
			Timestamp: time.Now().UTC().Format(time.RFC3339),
			Data: map[string]any{
				"duration_ms": result.DurationMs,
				"exit_code":   result.ExitCode,
			},
		})
	}

	return result
}

// buildArgs constructs Claude Code command arguments.
func (e *Executor) buildArgs(req *ExecuteRequest) []string {
	args := []string{
		req.Prompt,
		"-p",
	}

	// Add allowed tools
	allowedTools := req.AllowedTools
	if len(allowedTools) == 0 {
		allowedTools = defaultAllowedTools
	}
	for _, tool := range allowedTools {
		args = append(args, "--allowedTools", tool)
	}

	return args
}

// buildStreamArgs constructs Claude Code command arguments with streaming output.
func (e *Executor) buildStreamArgs(req *ExecuteRequest) []string {
	args := []string{
		req.Prompt,
		"-p",
		"--verbose",
		"--output-format", "stream-json",
	}

	// Add allowed tools
	allowedTools := req.AllowedTools
	if len(allowedTools) == 0 {
		allowedTools = defaultAllowedTools
	}
	for _, tool := range allowedTools {
		args = append(args, "--allowedTools", tool)
	}

	return args
}

// streamOutput reads from stdout and sends events.
func (e *Executor) streamOutput(r io.Reader, stream string, handler StreamEventHandler, output *strings.Builder) {
	scanner := bufio.NewScanner(r)
	buf := make([]byte, 0, 64*1024)
	scanner.Buffer(buf, 1024*1024)

	for scanner.Scan() {
		line := scanner.Text()
		if line == "" {
			continue
		}

		output.WriteString(line)
		output.WriteString("\n")

		handler(StreamEvent{
			Type:      "output",
			Content:   line,
			Stream:    stream,
			Timestamp: time.Now().UTC().Format(time.RFC3339),
		})
	}
}

// streamStderr reads from stderr and sends error events.
func (e *Executor) streamStderr(r io.Reader, handler StreamEventHandler) {
	scanner := bufio.NewScanner(r)
	buf := make([]byte, 0, 64*1024)
	scanner.Buffer(buf, 1024*1024)

	for scanner.Scan() {
		line := scanner.Text()
		if line == "" {
			continue
		}

		handler(StreamEvent{
			Type:      "error",
			Content:   line,
			Stream:    "stderr",
			Timestamp: time.Now().UTC().Format(time.RFC3339),
		})
	}
}
