package installer

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

	"gitgud.io/mike/mpv-manager/pkg/keyring"
	"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
	}
}

// RunSudoCommand runs a command with sudo, using keyring password if available
func (cr *CommandRunner) RunSudoCommand(name string, args ...string) error {
	// Already root - no sudo needed
	if os.Geteuid() == 0 {
		log.Debug("RunSudoCommand: Already running as root, no sudo needed")
		return cr.RunCommand(name, args...)
	}

	// Try to get password from keyring
	kr, err := keyring.Open()
	if err != nil {
		log.Debug(fmt.Sprintf("RunSudoCommand: Keyring not available: %v, falling back to regular sudo", err))
		// No keyring available - fall back to regular sudo
		return cr.RunCommand("sudo", append([]string{name}, args...)...)
	}

	password, err := kr.GetPassword()
	if err != nil {
		log.Debug("RunSudoCommand: No password stored in keyring, falling back to regular sudo")
		// No password stored - fall back to regular sudo
		return cr.RunCommand("sudo", append([]string{name}, args...)...)
	}

	log.Debug("RunSudoCommand: Using password from keyring")

	// Validate and cache sudo credentials
	if err := cr.cacheSudoCredentials(password); err != nil {
		log.Error(fmt.Sprintf("RunSudoCommand: Sudo authentication failed: %v", err))
		return fmt.Errorf("sudo authentication failed: %w", err)
	}

	log.Debug("RunSudoCommand: Sudo credentials cached, running command")

	// Run command with cached sudo
	return cr.RunCommand("sudo", append([]string{name}, args...)...)
}

// cacheSudoCredentials validates password and refreshes sudo timestamp
func (cr *CommandRunner) cacheSudoCredentials(password string) error {
	log.Debug("cacheSudoCredentials: Validating sudo password")

	cmd := exec.CommandContext(cr.ctx, "sudo", "-S", "-v")
	cmd.Stdin = bytes.NewBufferString(password + "\n")

	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	if err := cmd.Run(); err != nil {
		log.Debug(fmt.Sprintf("cacheSudoCredentials: Validation failed - stderr: %s", stderr.String()))
		return fmt.Errorf("invalid password: %s", stderr.String())
	}

	log.Debug("cacheSudoCredentials: Password validated, sudo timestamp refreshed")
	return nil
}

// RunSudoCommands runs multiple commands with sudo authentication
func (cr *CommandRunner) RunSudoCommands(commands []string) error {
	log.Debug(fmt.Sprintf("RunSudoCommands: Running %d commands with sudo", len(commands)))

	if os.Geteuid() == 0 {
		log.Debug("RunSudoCommands: Already running as root")
		// Already root, run commands directly
		for _, cmdStr := range commands {
			if err := cr.RunShellCommand(cmdStr); err != nil {
				return err
			}
		}
		return nil
	}

	// Get password from keyring
	kr, err := keyring.Open()
	if err != nil {
		log.Error(fmt.Sprintf("RunSudoCommands: Keyring not available: %v", err))
		return fmt.Errorf("no keyring available: %w", err)
	}

	password, err := kr.GetPassword()
	if err != nil {
		log.Error(fmt.Sprintf("RunSudoCommands: No password in keyring: %v", err))
		return fmt.Errorf("no sudo password available: %w", err)
	}

	log.Debug("RunSudoCommands: Got password from keyring, authenticating")

	// Authenticate once
	if err := cr.cacheSudoCredentials(password); err != nil {
		return err
	}

	log.Debug("RunSudoCommands: Authenticated, running commands")

	// Run all commands (sudo timestamp is cached)
	for i, cmdStr := range commands {
		select {
		case <-cr.ctx.Done():
			return fmt.Errorf("cancelled: %w", cr.ctx.Err())
		default:
		}

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