package installer

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"

	"gitgud.io/mike/mpv-manager/pkg/log"
)

// CommandRunner implements CommandExecutor interface
var _ CommandExecutor = (*CommandRunner)(nil)

type CommandRunner struct {
	outputChan chan<- string
	errorChan  chan<- error
	ctx        context.Context // Context for cancellation support
}

// NewCommandRunner creates a new command runner with output and error channels
func NewCommandRunner(outputChan chan<- string, errorChan chan<- error) *CommandRunner {
	return &CommandRunner{
		outputChan: outputChan,
		errorChan:  errorChan,
		ctx:        context.Background(), // Default to background context
	}
}

// NewCommandRunnerWithContext creates a new command runner with a specific context for cancellation
func NewCommandRunnerWithContext(ctx context.Context, outputChan chan<- string, errorChan chan<- error) *CommandRunner {
	return &CommandRunner{
		outputChan: outputChan,
		errorChan:  errorChan,
		ctx:        ctx,
	}
}

// Context returns the command runner's context
func (cr *CommandRunner) Context() context.Context {
	return cr.ctx
}

// SetContext sets the context for the command runner
func (cr *CommandRunner) SetContext(ctx context.Context) {
	cr.ctx = ctx
}

func (cr *CommandRunner) RunCommand(name string, args ...string) error {
	cmd := exec.CommandContext(cr.ctx, name, args...)
	return cr.RunCommandWithOutput(cmd)
}

func (cr *CommandRunner) RunCommandWithOutput(cmd *exec.Cmd) error {
	cmdStr := strings.Join(append([]string{cmd.Path}, cmd.Args[1:]...), " ")
	log.Command(cmdStr)

	if cr.outputChan != nil {
		cr.outputChan <- fmt.Sprintf("Running: %s", cmdStr)
		cr.outputChan <- strings.Repeat("─", 50)
	}

	var stdoutBuf, stderrBuf bytes.Buffer
	multiWriter := io.MultiWriter(&stdoutBuf, &stderrBuf)

	if cr.outputChan != nil {
		cmd.Stdout = io.MultiWriter(multiWriter, &outputCapture{outputChan: cr.outputChan})
		cmd.Stderr = io.MultiWriter(multiWriter, &outputCapture{outputChan: cr.outputChan, isError: true})
	} else {
		cmd.Stdout = multiWriter
		cmd.Stderr = multiWriter
	}

	if cmd.Stdin == nil {
		cmd.Stdin = os.Stdin
	}

	err := cmd.Run()

	output := stdoutBuf.String() + stderrBuf.String()
	if output != "" {
		log.Output(output)
	}

	if err != nil {
		log.Error(fmt.Sprintf("Command failed: %s - Error: %s", cmdStr, err.Error()))
		if cr.errorChan != nil {
			cr.errorChan <- err
		}
		if cr.outputChan != nil {
			cr.outputChan <- fmt.Sprintf("Error: %s", err.Error())
		}
		return err
	}

	log.Info("Command completed successfully")
	return nil
}

type outputCapture struct {
	outputChan chan<- string
	isError    bool
}

func (oc *outputCapture) Write(p []byte) (n int, err error) {
	lines := strings.Split(strings.TrimSpace(string(p)), "\n")
	for _, line := range lines {
		if line != "" {
			if oc.isError {
				log.ErrorOutput(line)
			} else {
				log.Output(line)
			}
			if oc.outputChan != nil {
				oc.outputChan <- line
			}
		}
	}
	return len(p), nil
}

func (cr *CommandRunner) RunShellCommand(script string) error {
	// Use bash for Linux/macOS, fall back to sh on Windows
	shell := "/bin/bash"
	if _, err := os.Stat(shell); os.IsNotExist(err) {
		shell = "sh"
	}
	cmd := exec.CommandContext(cr.ctx, shell, "-c", script)
	return cr.RunCommandWithOutput(cmd)
}

func (cr *CommandRunner) RunCommands(commands []string) error {
	for i, cmdStr := range commands {
		// Check for cancellation between commands
		select {
		case <-cr.ctx.Done():
			return fmt.Errorf("command execution cancelled: %w", cr.ctx.Err())
		default:
		}

		log.Separator(fmt.Sprintf("Command %d", i+1))
		if err := cr.RunShellCommand(cmdStr); err != nil {
			return err
		}
	}
	return nil
}

// IsCancelled returns true if the context has been cancelled
func (cr *CommandRunner) IsCancelled() bool {
	select {
	case <-cr.ctx.Done():
		return true
	default:
		return false
	}
}

// CheckCancelled returns an error if the context has been cancelled
func (cr *CommandRunner) CheckCancelled() error {
	select {
	case <-cr.ctx.Done():
		return fmt.Errorf("operation cancelled: %w", cr.ctx.Err())
	default:
		return nil
	}
}
