package statusreporter

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

type Snapshot struct {
	Status                string
	ServerConnectivity    string
	ScreenID              string
	ServerBaseURL         string
	MQTTBroker            string
	HeartbeatEverySeconds int
	StartedAt             time.Time
	LastHeartbeatAt       time.Time
	DisplayState          string
}

type statusPayload struct {
	ScreenID              string `json:"screen_id"`
	Timestamp             string `json:"ts"`
	Status                string `json:"status"`
	ServerConnectivity    string `json:"server_connectivity"`
	ServerURL             string `json:"server_url"`
	MQTTBroker            string `json:"mqtt_broker"`
	HeartbeatEverySeconds int    `json:"heartbeat_every_seconds"`
	StartedAt             string `json:"started_at,omitempty"`
	LastHeartbeatAt       string `json:"last_heartbeat_at,omitempty"`
	DisplayState          string `json:"display_state,omitempty"`
}

// MQTTConfig holds the MQTT broker configuration returned by the server in the
// status-report response.  All fields are empty when the server did not send
// a mqtt object (e.g. MQTT is disabled on the server side).
type MQTTConfig struct {
	Broker   string
	Username string
	Password string
}

// serverResponse is the JSON body returned by POST /api/v1/player/status.
type serverResponse struct {
	Status string           `json:"status"`
	MQTT   *serverMQTTBlock `json:"mqtt,omitempty"`
}

type serverMQTTBlock struct {
	Broker   string `json:"broker"`
	Username string `json:"username"`
	Password string `json:"password"`
}

type Reporter struct {
	baseURL string
	client  *http.Client
	now     func() time.Time
}

func New(baseURL string, client *http.Client, now func() time.Time) *Reporter {
	if client == nil {
		client = &http.Client{Timeout: 5 * time.Second}
	}

	if now == nil {
		now = time.Now
	}

	return &Reporter{
		baseURL: strings.TrimRight(baseURL, "/"),
		client:  client,
		now:     now,
	}
}

// Send reports the snapshot to the server and returns the MQTT configuration
// provided by the server in the response body.  If the server does not include
// a mqtt object the returned MQTTConfig will have an empty Broker field.
func (r *Reporter) Send(ctx context.Context, snapshot Snapshot) (MQTTConfig, error) {
	payload := buildPayload(snapshot, r.now())
	body, err := json.Marshal(payload)
	if err != nil {
		return MQTTConfig{}, fmt.Errorf("marshal status payload: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, r.baseURL+"/api/v1/player/status", bytes.NewReader(body))
	if err != nil {
		return MQTTConfig{}, fmt.Errorf("build status request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")

	resp, err := r.client.Do(req)
	if err != nil {
		return MQTTConfig{}, fmt.Errorf("send status request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return MQTTConfig{}, fmt.Errorf("unexpected status response: %s", resp.Status)
	}

	var sr serverResponse
	if err := json.NewDecoder(resp.Body).Decode(&sr); err != nil {
		// Non-fatal: decoding failure just means no MQTT config update.
		return MQTTConfig{}, nil
	}

	if sr.MQTT != nil {
		return MQTTConfig{
			Broker:   sr.MQTT.Broker,
			Username: sr.MQTT.Username,
			Password: sr.MQTT.Password,
		}, nil
	}

	return MQTTConfig{}, nil
}

func buildPayload(snapshot Snapshot, now time.Time) statusPayload {
	payload := statusPayload{
		ScreenID:              snapshot.ScreenID,
		Timestamp:             now.Format(time.RFC3339),
		Status:                snapshot.Status,
		ServerConnectivity:    snapshot.ServerConnectivity,
		ServerURL:             snapshot.ServerBaseURL,
		MQTTBroker:            snapshot.MQTTBroker,
		HeartbeatEverySeconds: snapshot.HeartbeatEverySeconds,
	}

	if !snapshot.StartedAt.IsZero() {
		payload.StartedAt = snapshot.StartedAt.Format(time.RFC3339)
	}

	if !snapshot.LastHeartbeatAt.IsZero() {
		payload.LastHeartbeatAt = snapshot.LastHeartbeatAt.Format(time.RFC3339)
	}

	if snapshot.DisplayState != "" {
		payload.DisplayState = snapshot.DisplayState
	}

	return payload
}
