package platform

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

// ClipboardMaxBytes mirrors the backend's CDROP_CLIPBOARD_MAX_BYTES (default
// 64 KiB). Uploads larger than this are rejected client-side, matching the web.
const ClipboardMaxBytes = 64 * 1024

// Client is an authenticated client for the cdrop backend API. The access token
// is held here in Go (never in the WebView); deviceName identifies this device
// as the source of clipboard writes via the X-Device-Name header.
type Client struct {
	baseURL    string
	token      string
	deviceName string
	http       *http.Client
}

// NewClient builds a Client. baseURL is the backend origin, e.g.
// https://drop.commilitia.net.
func NewClient(baseURL, accessToken, deviceName string) *Client {
	return &Client{
		baseURL:    strings.TrimRight(baseURL, "/"),
		token:      accessToken,
		deviceName: deviceName,
		http:       &http.Client{Timeout: 30 * time.Second},
	}
}

// ClipboardContent is the cloud clipboard state. UpdatedAt is the server's
// authoritative timestamp (also the ETag); 0 means never written.
type ClipboardContent struct {
	Content      string `json:"content"`
	ContentType  string `json:"content_type"`
	SourceDevice string `json:"source_device"`
	UpdatedAt    int64  `json:"updated_at"`
}

func (c *Client) authReq(ctx context.Context, method, path string, body io.Reader) (*http.Request, error) {
	req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+c.token)
	req.Header.Set("Accept", "application/json")
	if c.deviceName != "" {
		req.Header.Set("X-Device-Name", c.deviceName)
	}
	return req, nil
}

// Clipboard fetches the current cloud clipboard content.
func (c *Client) Clipboard(ctx context.Context) (ClipboardContent, error) {
	req, err := c.authReq(ctx, http.MethodGet, "/api/clipboard", nil)
	if err != nil {
		return ClipboardContent{}, fmt.Errorf("api: build clipboard request: %w", err)
	}
	resp, err := c.http.Do(req)
	if err != nil {
		return ClipboardContent{}, fmt.Errorf("api: clipboard request: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode/100 != 2 {
		return ClipboardContent{}, fmt.Errorf("api: GET clipboard status %d", resp.StatusCode)
	}
	var cc ClipboardContent
	if err := json.NewDecoder(resp.Body).Decode(&cc); err != nil {
		return ClipboardContent{}, fmt.Errorf("api: decode clipboard: %w", err)
	}
	return cc, nil
}

// PutClipboard uploads plain text to the cloud clipboard.
func (c *Client) PutClipboard(ctx context.Context, text string) error {
	if len(text) > ClipboardMaxBytes {
		return fmt.Errorf("api: clipboard content %d bytes exceeds limit %d", len(text), ClipboardMaxBytes)
	}
	payload, err := json.Marshal(map[string]string{
		"content":      text,
		"content_type": "text/plain",
	})
	if err != nil {
		return fmt.Errorf("api: marshal clipboard: %w", err)
	}
	req, err := c.authReq(ctx, http.MethodPut, "/api/clipboard", bytes.NewReader(payload))
	if err != nil {
		return fmt.Errorf("api: build clipboard request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := c.http.Do(req)
	if err != nil {
		return fmt.Errorf("api: clipboard upload: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode/100 != 2 {
		return fmt.Errorf("api: PUT clipboard status %d", resp.StatusCode)
	}
	return nil
}
