package daemon

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"

	"github.com/antitbone/dwarfctl/internal/odometry"
)

// Client is a thin HTTP client that talks to the daemon.
type Client struct {
	baseURL string
	http    *http.Client
}

// NewClient creates a daemon client for the given address (e.g. "127.0.0.1:7777").
func NewClient(addr string) *Client {
	if addr == "" {
		addr = DefaultAddr
	}
	return &Client{
		baseURL: "http://" + addr,
		http:    &http.Client{Timeout: 60 * time.Second},
	}
}

// IsAlive checks if the daemon is reachable.
func (c *Client) IsAlive() bool {
	resp, err := c.http.Get(c.baseURL + "/health")
	return err == nil && resp.StatusCode == 200
}

// Orientation returns the current cumulative orientation from the daemon.
func (c *Client) Orientation() (odometry.Orientation, error) {
	resp, err := c.http.Get(c.baseURL + "/orientation")
	if err != nil {
		return odometry.Orientation{}, err
	}
	defer resp.Body.Close()
	var o odometry.Orientation
	return o, json.NewDecoder(resp.Body).Decode(&o)
}

// Grab captures a frame. If path is empty, the daemon picks a temp path.
// Returns the saved file path.
func (c *Client) Grab(path string) (string, error) {
	url := c.baseURL + "/grab"
	if path != "" {
		url += "?path=" + path
	}
	resp, err := c.http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		body, _ := io.ReadAll(resp.Body)
		return "", fmt.Errorf("daemon: %s", body)
	}
	var result map[string]string
	json.NewDecoder(resp.Body).Decode(&result)
	return result["path"], nil
}

// Reset zeros the odometry.
func (c *Client) Reset() error {
	_, err := c.http.Post(c.baseURL+"/reset", "", nil)
	return err
}

// Slew sends a joystick vector.
func (c *Client) Slew(angle, length float64) error {
	body, _ := json.Marshal(map[string]float64{"angle": angle, "length": length})
	resp, err := c.http.Post(c.baseURL+"/slew", "application/json", bytes.NewReader(body))
	if err != nil {
		return err
	}
	resp.Body.Close()
	return nil
}

// StopMotors stops all motors.
func (c *Client) StopMotors() error {
	_, err := c.http.Post(c.baseURL+"/stop", "", nil)
	return err
}

// Camera opens or closes a camera.
func (c *Client) Camera(action, cam string) error {
	url := fmt.Sprintf("%s/camera?action=%s&cam=%s", c.baseURL, action, cam)
	resp, err := c.http.Post(url, "", nil)
	if err != nil {
		return err
	}
	resp.Body.Close()
	return nil
}

// SetFoV overrides the field of view.
func (c *Client) SetFoV(h, v float64) error {
	body, _ := json.Marshal(map[string]float64{"h": h, "v": v})
	resp, err := c.http.Post(c.baseURL+"/fov", "application/json", bytes.NewReader(body))
	if err != nil {
		return err
	}
	resp.Body.Close()
	return nil
}
