package tools

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

// Operator represents a chain operator between commands.
type Operator int

const (
	OpNone     Operator = iota
	OpAnd               // &&
	OpOr                // ||
	OpSeq               // ;
	OpPipe              // |
	OpRedirect          // >
	OpAppend            // >>
)

// Segment is a single command in a chain.
type Segment struct {
	Raw        string
	Op         Operator // operator AFTER this segment
	RedirectTo string   // file path for > or >>
	IsAppend   bool     // true for >>, false for >
}

// ParseChain splits a command string into segments by &&, ;, |, >, and >>.
// Respects quoted strings (single and double quotes).
func ParseChain(input string) []Segment {
	var segments []Segment
	var current strings.Builder
	runes := []rune(input)
	n := len(runes)
	for i := 0; i < n; i++ {
		ch := runes[i]

		// handle quotes
		if ch == '\'' || ch == '"' {
			quote := ch
			current.WriteRune(ch)
			i++
			for i < n && runes[i] != quote {
				current.WriteRune(runes[i])
				i++
			}
			if i < n {
				current.WriteRune(runes[i])
			}
			continue
		}
		// >>
		if ch == '>' && i+1 < n && runes[i+1] == '>' {
			cmd := strings.TrimSpace(current.String())
			if cmd != "" {
				segments = append(segments, Segment{
					Raw: cmd,
					Op:  OpAppend,
				})
			}
			current.Reset()
			i++ // skip second >
			continue
		}
		// >
		if ch == '>' {
			cmd := strings.TrimSpace(current.String())
			if cmd != "" {
				segments = append(segments, Segment{
					Raw: cmd,
					Op:  OpRedirect,
				})
			}
			current.Reset()
			continue
		}
		// &&
		if ch == '&' && i+1 < n && runes[i+1] == '&' {
			segments = append(segments, Segment{
				Raw: strings.TrimSpace(current.String()),
				Op:  OpAnd,
			})
			current.Reset()
			i++ // skip second &
			continue
		}
		// ;
		if ch == ';' {
			segments = append(segments, Segment{
				Raw: strings.TrimSpace(current.String()),
				Op:  OpSeq,
			})
			current.Reset()
			continue
		}
		// ||
		if ch == '|' && i+1 < n && runes[i+1] == '|' {
			segments = append(segments, Segment{
				Raw: strings.TrimSpace(current.String()),
				Op:  OpOr,
			})
			current.Reset()
			i++ // skip second |
			continue
		}
		// | (single pipe)
		if ch == '|' {
			segments = append(segments, Segment{
				Raw: strings.TrimSpace(current.String()),
				Op:  OpPipe,
			})
			current.Reset()
			continue
		}
		current.WriteRune(ch)
	}
	// last segment
	last := strings.TrimSpace(current.String())
	if last != "" {
		segments = append(segments, Segment{Raw: last, Op: OpNone})
	}
	return segments
}

// ExecChain executes a command string with pipe/chaining support.
// Returns the combined output of all commands.
func ExecChain(command string) string {
	segments := ParseChain(command)
	if len(segments) == 0 {
		return "[error] empty command"
	}

	// Check if we have a redirect
	var redirectTo string
	var isAppend bool
	redirectIdx := -1
	for i, seg := range segments {
		if seg.Op == OpRedirect || seg.Op == OpAppend {
			redirectIdx = i
			isAppend = seg.Op == OpAppend
			break
		}
	}

	if redirectIdx >= 0 && redirectIdx+1 < len(segments) {
		targetPath, err := resolveRedirectPath(segments[redirectIdx+1].Raw)
		if err != nil {
			return fmt.Sprintf("[error] redirect: %v", err)
		}
		redirectTo = targetPath
		redirectCmd := segments[redirectIdx].Raw

		// Check if there's chaining after redirect by looking at the operator of the segment AFTER the target
		// The segment at redirectIdx+1 is the target file, and its Op tells us what operator follows
		hasChainingAfterRedirect := false
		if redirectIdx+1 < len(segments) && segments[redirectIdx+1].Op != OpNone && segments[redirectIdx+1].Op != OpPipe {
			hasChainingAfterRedirect = true
		}

		// Remove both the redirect segment and its target
		segments = append(segments[:redirectIdx], segments[redirectIdx+2:]...)

		// Execute the redirect command
		var lastOutput string
		var lastErr error
		lastOutput, lastErr = execSingle(redirectCmd, "")
		if lastErr != nil {
			return fmt.Sprintf("[error] redirect: %v", lastErr)
		}
		if err := writeFile(redirectTo, lastOutput, isAppend); err != nil {
			return fmt.Sprintf("[error] redirect: %v", err)
		}
		mode := "Wrote"
		if isAppend {
			mode = "Appended"
		}
		size := humanSizeChain(int64(len(lastOutput)))
		redirectResult := fmt.Sprintf("%s %s → %s", mode, size, filepath.Base(redirectTo))

		// If no remaining segments or no chaining, just return the write confirmation
		if len(segments) == 0 || !hasChainingAfterRedirect {
			return redirectResult
		}

		// There are remaining commands after the redirect
		collected := []string{redirectResult}

		// Execute remaining commands
		for _, seg := range segments {
			lastOutput, lastErr = execSingle(seg.Raw, "")
			if lastOutput != "" {
				collected = append(collected, lastOutput)
			}
		}
		return strings.Join(collected, "\n")
	} else if redirectIdx >= 0 && redirectIdx+1 >= len(segments) {
		return "[error] redirect: target file required"
	}

	var collected []string
	var lastOutput string
	var lastErr error
	pipeInput := ""
	for i, seg := range segments {
		if i > 0 {
			prevOp := segments[i-1].Op
			if prevOp == OpAnd && lastErr != nil {
				continue
			}
			if prevOp == OpOr && lastErr == nil {
				continue
			}
		}
		segStdin := ""
		if i == 0 {
			segStdin = pipeInput
		} else if segments[i-1].Op == OpPipe {
			segStdin = lastOutput
		}
		lastOutput, lastErr = execSingle(seg.Raw, segStdin)
		if i < len(segments)-1 && seg.Op == OpPipe {
			continue
		}
		if lastOutput != "" {
			collected = append(collected, lastOutput)
		}
	}

	// Handle redirect if present
	if redirectTo != "" {
		output := lastOutput
		if err := writeFile(redirectTo, output, isAppend); err != nil {
			return fmt.Sprintf("[error] redirect: %v", err)
		}
		mode := "Wrote"
		if isAppend {
			mode = "Appended"
		}
		size := humanSizeChain(int64(len(output)))
		return fmt.Sprintf("%s %s → %s", mode, size, filepath.Base(redirectTo))
	}

	return strings.Join(collected, "\n")
}

// execSingle executes a single command (with arguments) and returns output and error.
func execSingle(command, stdin string) (string, error) {
	parts := tokenize(command)
	if len(parts) == 0 {
		return "", errors.New("empty command")
	}
	name := parts[0]
	args := parts[1:]
	// Check if it's a built-in Go command
	result, err := execBuiltin(name, args, stdin)
	if err == nil {
		return result, nil
	}
	// Check if it's a "not a builtin" error (meaning we should try system command)
	if err.Error() == "not a builtin" {
		// Execute as system command
		cmd := exec.Command(name, args...)
		if stdin != "" {
			cmd.Stdin = strings.NewReader(stdin)
		}
		output, err := cmd.CombinedOutput()
		if err != nil {
			return string(output), err
		}
		return string(output), nil
	}
	// It's a builtin that returned an error
	return result, err
}

// tokenize splits a command string by whitespace, respecting quotes.
func tokenize(input string) []string {
	var tokens []string
	var current strings.Builder
	inQuote := false
	var quoteChar rune
	for _, ch := range input {
		if inQuote {
			if ch == quoteChar {
				inQuote = false
			} else {
				current.WriteRune(ch)
			}
			continue
		}
		if ch == '\'' || ch == '"' {
			inQuote = true
			quoteChar = ch
			continue
		}
		if ch == ' ' || ch == '\t' {
			if current.Len() > 0 {
				tokens = append(tokens, current.String())
				current.Reset()
			}
			continue
		}
		current.WriteRune(ch)
	}
	if current.Len() > 0 {
		tokens = append(tokens, current.String())
	}
	return tokens
}

// execBuiltin executes a built-in command if it exists.
func execBuiltin(name string, args []string, stdin string) (string, error) {
	var result string
	switch name {
	case "echo":
		result = FsEcho(args, stdin)
	case "time":
		result = FsTime(args, stdin)
	case "cat":
		result = FsCat(args, stdin)
	case "pwd":
		result = FsPwd(args, stdin)
	case "cd":
		result = FsCd(args, stdin)
	case "mkdir":
		result = FsMkdir(args, stdin)
	case "ls":
		result = FsLs(args, stdin)
	case "cp":
		result = FsCp(args, stdin)
	case "mv":
		result = FsMv(args, stdin)
	case "rm":
		result = FsRm(args, stdin)
	case "grep":
		result = FsGrep(args, stdin)
	case "head":
		result = FsHead(args, stdin)
	case "tail":
		result = FsTail(args, stdin)
	case "wc":
		result = FsWc(args, stdin)
	case "sort":
		result = FsSort(args, stdin)
	case "uniq":
		result = FsUniq(args, stdin)
	case "sed":
		result = FsSed(args, stdin)
	case "stat":
		result = FsStat(args, stdin)
	case "go":
		if len(args) == 0 {
			return "[error] usage: go <subcommand> [options]", nil
		}
		cmd := exec.Command("go", args...)
		cmd.Dir = cfg.FilePickerDir
		output, err := cmd.CombinedOutput()
		if err != nil {
			return fmt.Sprintf("[error] go %s: %v\n%s", args[0], err, string(output)), nil
		}
		return string(output), nil
	default:
		return "", errors.New("not a builtin")
	}
	if strings.HasPrefix(result, "[error]") {
		return result, errors.New(result)
	}
	return result, nil
}

// resolveRedirectPath resolves the target path for a redirect operator
func resolveRedirectPath(path string) (string, error) {
	path = strings.TrimSpace(path)
	if path == "" {
		return "", errors.New("redirect target required")
	}
	abs, err := resolvePath(path)
	if err != nil {
		return "", err
	}
	return abs, nil
}

// writeFile writes content to a file (truncate or append)
func writeFile(path, content string, append bool) error {
	flags := os.O_CREATE | os.O_WRONLY
	if append {
		flags |= os.O_APPEND
	} else {
		flags |= os.O_TRUNC
	}
	f, err := os.OpenFile(path, flags, 0644)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = f.WriteString(content)
	return err
}

// humanSizeChain returns human-readable file size
func humanSizeChain(n int64) string {
	switch {
	case n >= 1<<20:
		return fmt.Sprintf("%.1fMB", float64(n)/float64(1<<20))
	case n >= 1<<10:
		return fmt.Sprintf("%.1fKB", float64(n)/float64(1<<10))
	default:
		return fmt.Sprintf("%dB", n)
	}
}
