package proxmox

import (
	"bytes"
	"context"
	"fmt"
	"io"

	"github.com/gorilla/websocket"
	"github.com/luthermonson/go-proxmox"
)

// NodeCommand connects to the Proxmox VNC websocket and streams command output.
// It returns an io.ReadCloser that streams the command output.
func (n *Node) NodeCommand(ctx context.Context, command string) (io.ReadCloser, error) {
	if !n.client.HasSession() {
		return nil, ErrNoSession
	}

	node := proxmox.NewNode(n.client.Client, n.name)
	term, err := node.TermProxy(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to get term proxy: %w", err)
	}

	send, recv, errs, closeWS, err := node.TermWebSocket(term)
	if err != nil {
		return nil, fmt.Errorf("failed to connect to term websocket: %w", err)
	}

	// Wrap the websocket closer to also close HTTP transport connections.
	// This prevents goroutine leaks when streaming connections are interrupted.
	httpClient := n.client.GetHTTPClient()
	closeFn := func() error {
		closeTransportConnections(httpClient)
		return closeWS()
	}

	handleSend := func(data []byte) error {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case send <- data:
			return nil
		case err := <-errs:
			return fmt.Errorf("failed to send: %w", err)
		}
	}

	// Send command
	cmd := []byte(command + "\n")
	if err := handleSend(cmd); err != nil {
		closeFn()
		return nil, err
	}

	// Create a pipe to stream the websocket messages
	pr, pw := io.Pipe()

	// Command line without trailing newline for matching in output
	cmdLine := cmd[:len(cmd)-1]

	// Start a goroutine to read from websocket and write to pipe
	go func() {
		defer closeFn()
		defer pw.Close()

		seenCommand := false
		shouldSkip := true

		for {
			select {
			case <-ctx.Done():
				_ = pw.CloseWithError(ctx.Err())
				return
			case msg := <-recv:
				// skip the header message like

				// Linux pve 6.17.4-1-pve #1 SMP PREEMPT_DYNAMIC PMX 6.17.4-1 (2025-12-03T15:42Z) x86_64
				//
				// The programs included with the Debian GNU/Linux system are free software;
				// the exact distribution terms for each program are described in the
				// individual files in /usr/share/doc/*/copyright.
				//
				// Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
				// permitted by applicable law.
				//
				// root@pve:~# pct exec 101 -- journalctl -u "sftpgo" -f
				//
				// send begins after the line above
				if shouldSkip {
					// First, check if this message contains our command echo
					if !seenCommand && bytes.Contains(msg, cmdLine) {
						seenCommand = true
					}
					// Only stop skipping after we've seen the command AND output markers
					if seenCommand {
						if bytes.Contains(msg, []byte("\x1b[H")) || // watch cursor home
							bytes.Contains(msg, []byte("\x1b[?2004l")) { // bracket paste OFF (command ended)
							shouldSkip = false
						}
					}
					continue
				}
				if _, err := pw.Write(msg); err != nil {
					return
				}
			case err := <-errs:
				if err != nil {
					if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
						return
					}
					_ = pw.CloseWithError(err)
					return
				}
			}
		}
	}()

	return pr, nil
}

// NodeJournalctl streams journalctl output for the given service.
//
// If services are not empty, it will be used to filter the output by services.
// If limit is greater than 0, it will be used to limit the number of lines of output.
func (n *Node) NodeJournalctl(ctx context.Context, services []string, limit int) (io.ReadCloser, error) {
	command, err := formatJournalctl(services, limit)
	if err != nil {
		return nil, err
	}
	return n.NodeCommand(ctx, command)
}

// NodeTail streams tail output for the given file.
//
// If limit is greater than 0, it will be used to limit the number of lines of output.
func (n *Node) NodeTail(ctx context.Context, files []string, limit int) (io.ReadCloser, error) {
	command, err := formatTail(files, limit)
	if err != nil {
		return nil, err
	}
	return n.NodeCommand(ctx, command)
}
