package openrgb

import (
	"fmt"
	"log"
	"net"
	"os/exec"
	"strconv"
	"time"
)

const (
	maxRetries = 4
	retryDelay = 3 * time.Second
)

// Client controls RGB devices via the openrgb CLI. It talks to a running
// OpenRGB SDK server (the --startminimized --server instance) rather than
// re-detecting hardware on every command — local re-detection races with the
// server for the USB/HID devices during login/boot and is unreliable.
type Client struct {
	bin    string
	server string // host:port of the OpenRGB SDK server
}

func Connect(host string, port int) (*Client, error) {
	bin, err := exec.LookPath("openrgb")
	if err != nil {
		return nil, fmt.Errorf("openrgb not found in PATH: %w", err)
	}

	server := net.JoinHostPort(host, strconv.Itoa(port))

	// Verify the SDK server is reachable (confirms it's up and enumerated).
	cmd := exec.Command(bin, "--client", server, "--list-devices")
	if out, err := cmd.CombinedOutput(); err != nil {
		return nil, fmt.Errorf("openrgb client connect to %s failed: %w\n%s", server, err, out)
	}

	return &Client{bin: bin, server: server}, nil
}

func (c *Client) Close() error {
	return nil
}

// run executes an openrgb command with retries to handle transient failures
// during early boot when device enumeration may not be complete.
func (c *Client) run(args ...string) error {
	full := append([]string{"--client", c.server}, args...)
	var lastErr error
	for attempt := range maxRetries {
		cmd := exec.Command(c.bin, full...)
		out, err := cmd.CombinedOutput()
		if err == nil {
			return nil
		}
		lastErr = fmt.Errorf("openrgb %v: %w\n%s", args, err, out)
		if attempt < maxRetries-1 {
			log.Printf("openrgb command failed (attempt %d/%d), retrying in %v: %v",
				attempt+1, maxRetries, retryDelay, lastErr)
			time.Sleep(retryDelay)
		}
	}
	return lastErr
}

// SetAllOff sets all devices to black (off).
//
// Uses "static" mode, not "direct": OpenRGB's direct-mode color path crashes
// (vector out-of-bounds → SIGABRT) on devices that expose addressable zones
// with zero LEDs, e.g. unused ASUS Aura ARGB headers. Static mode applies the
// same color across all devices and zones without hitting that path.
func (c *Client) SetAllOff() error {
	return c.run("--mode", "static", "--color", "000000")
}

// SetAllOn sets all devices to the given color. See SetAllOff for why this uses
// "static" mode rather than "direct".
func (c *Client) SetAllOn(r, g, b byte) error {
	color := fmt.Sprintf("%02X%02X%02X", r, g, b)
	return c.run("--mode", "static", "--color", color)
}
