package httpapi

import (
	"errors"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"
)

type screenStatusSummary struct {
	Total    int `json:"total"`
	Online   int `json:"online"`
	Degraded int `json:"degraded"`
	Offline  int `json:"offline"`
	Stale    int `json:"stale"`
}

type screenStatusOverview struct {
	Summary screenStatusSummary  `json:"summary"`
	Screens []playerStatusRecord `json:"screens"`
}

var allowedStatuses = map[string]struct{}{
	"starting": {},
	"running":  {},
	"stopped":  {},
}

var allowedServerConnectivity = map[string]struct{}{
	"":         {},
	"unknown":  {},
	"online":   {},
	"degraded": {},
	"offline":  {},
}

type playerStatusRequest 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"`
	LastHeartbeatAt       string `json:"last_heartbeat_at"`
}

func handlePlayerStatus(store playerStatusStore) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var request playerStatusRequest
		if err := decodeJSON(r, &request); err != nil {
			writeError(w, http.StatusBadRequest, "invalid_json", "ungueltiger JSON-Body", nil)
			return
		}

		if strings.TrimSpace(request.ScreenID) == "" {
			writeError(w, http.StatusBadRequest, "screen_id_required", "screen_id ist erforderlich", nil)
			return
		}
		request.ScreenID = strings.TrimSpace(request.ScreenID)

		if strings.TrimSpace(request.Timestamp) == "" {
			writeError(w, http.StatusBadRequest, "timestamp_required", "ts ist erforderlich", nil)
			return
		}

		if strings.TrimSpace(request.Status) == "" {
			writeError(w, http.StatusBadRequest, "status_required", "status ist erforderlich", nil)
			return
		}
		request.Status = strings.TrimSpace(request.Status)
		if _, ok := allowedStatuses[request.Status]; !ok {
			writeError(w, http.StatusBadRequest, "invalid_status", "status ist ungueltig", nil)
			return
		}

		request.ServerConnectivity = strings.TrimSpace(request.ServerConnectivity)
		if _, ok := allowedServerConnectivity[request.ServerConnectivity]; !ok {
			writeError(w, http.StatusBadRequest, "invalid_server_connectivity", "server_connectivity ist ungueltig", nil)
			return
		}

		if request.HeartbeatEverySeconds <= 0 {
			writeError(w, http.StatusBadRequest, "invalid_heartbeat_interval", "heartbeat_every_seconds muss positiv sein", nil)
			return
		}

		if err := validateOptionalRFC3339(request.Timestamp); err != nil {
			writeError(w, http.StatusBadRequest, "invalid_timestamp", "ts ist kein gueltiger RFC3339-Zeitstempel", nil)
			return
		}

		if err := validateOptionalRFC3339(request.StartedAt); err != nil {
			writeError(w, http.StatusBadRequest, "invalid_started_at", "started_at ist kein gueltiger RFC3339-Zeitstempel", nil)
			return
		}

		if err := validateOptionalRFC3339(request.LastHeartbeatAt); err != nil {
			writeError(w, http.StatusBadRequest, "invalid_last_heartbeat_at", "last_heartbeat_at ist kein gueltiger RFC3339-Zeitstempel", nil)
			return
		}

		store.Save(playerStatusRecord{
			ScreenID:              request.ScreenID,
			Timestamp:             request.Timestamp,
			Status:                request.Status,
			ServerConnectivity:    request.ServerConnectivity,
			ServerURL:             request.ServerURL,
			MQTTBroker:            request.MQTTBroker,
			HeartbeatEverySeconds: request.HeartbeatEverySeconds,
			StartedAt:             request.StartedAt,
			LastHeartbeatAt:       request.LastHeartbeatAt,
		})

		writeJSON(w, http.StatusOK, map[string]string{
			"status": "accepted",
		})
	}
}

func handleGetLatestPlayerStatus(store playerStatusStore) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		screenID := strings.TrimSpace(r.PathValue("screenId"))
		if screenID == "" {
			writeError(w, http.StatusBadRequest, "screen_id_required", "screenId ist erforderlich", nil)
			return
		}

		record, ok := store.Get(screenID)
		if !ok {
			writeError(w, http.StatusNotFound, "screen_status_not_found", "Fuer diesen Screen liegt noch kein Status vor", nil)
			return
		}

		record.Stale = isStale(record, store.Now())
		record.DerivedState = deriveState(record)

		writeJSON(w, http.StatusOK, record)
	}
}

func handleDeletePlayerStatus(store playerStatusStore) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		screenID := strings.TrimSpace(r.PathValue("screenId"))
		if screenID == "" {
			writeError(w, http.StatusBadRequest, "screen_id_required", "screenId ist erforderlich", nil)
			return
		}
		if !store.Delete(screenID) {
			writeError(w, http.StatusNotFound, "screen_status_not_found", "Fuer diesen Screen liegt noch kein Status vor", nil)
			return
		}
		writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
	}
}

func handleListLatestPlayerStatuses(store playerStatusStore) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		overview, err := buildScreenStatusOverview(store, r.URL.Query())
		if err != nil {
			writeOverviewQueryError(w, err)
			return
		}

		writeJSON(w, http.StatusOK, overview)
	}
}

func buildScreenStatusOverview(store playerStatusStore, query url.Values) (screenStatusOverview, error) {
	records := store.List()

	screenIDFilter := strings.ToLower(strings.TrimSpace(query.Get("q")))

	wantConnectivity := strings.TrimSpace(query.Get("server_connectivity"))
	switch wantConnectivity {
	case "", "online", "degraded", "offline", "unknown":
		// valid
	default:
		return screenStatusOverview{}, errInvalidServerConnectivity
	}

	wantStale := strings.TrimSpace(query.Get("stale"))
	switch wantStale {
	case "", "true", "false":
		// valid
	default:
		return screenStatusOverview{}, errInvalidStale
	}

	wantDerivedState := strings.TrimSpace(query.Get("derived_state"))
	switch wantDerivedState {
	case "", "online", "degraded", "offline":
		// valid
	default:
		return screenStatusOverview{}, errInvalidDerivedState
	}

	updatedSince, err := parseOptionalRFC3339(query.Get("updated_since"))
	if err != nil {
		return screenStatusOverview{}, errInvalidUpdatedSince
	}
	limit, err := parseOptionalPositiveInt(query.Get("limit"))
	if err != nil {
		return screenStatusOverview{}, errInvalidLimit
	}

	overview := screenStatusOverview{
		Screens: make([]playerStatusRecord, 0, len(records)),
	}

	for i := range records {
		records[i].Stale = isStale(records[i], store.Now())
		records[i].DerivedState = deriveState(records[i])
		overview.Summary.Total++
		switch records[i].DerivedState {
		case "offline":
			overview.Summary.Offline++
		case "degraded":
			overview.Summary.Degraded++
		default:
			overview.Summary.Online++
		}
		if records[i].Stale {
			overview.Summary.Stale++
		}
		if screenIDFilter != "" && !strings.Contains(strings.ToLower(records[i].ScreenID), screenIDFilter) {
			continue
		}
		if updatedSince != nil && !isUpdatedSince(records[i], *updatedSince) {
			continue
		}
		if wantConnectivity != "" && records[i].ServerConnectivity != wantConnectivity {
			continue
		}
		if wantStale != "" {
			if wantStale == "true" && !records[i].Stale {
				continue
			}
			if wantStale == "false" && records[i].Stale {
				continue
			}
		}
		if wantDerivedState != "" && records[i].DerivedState != wantDerivedState {
			continue
		}
		overview.Screens = append(overview.Screens, records[i])
	}

	sort.Slice(overview.Screens, func(i, j int) bool {
		leftPriority := derivedStatePriority(overview.Screens[i].DerivedState)
		rightPriority := derivedStatePriority(overview.Screens[j].DerivedState)
		if leftPriority != rightPriority {
			return leftPriority < rightPriority
		}

		return overview.Screens[i].ScreenID < overview.Screens[j].ScreenID
	})

	if limit > 0 && len(overview.Screens) > limit {
		overview.Screens = overview.Screens[:limit]
	}

	return overview, nil
}

var (
	errInvalidUpdatedSince       = errors.New("invalid updated_since")
	errInvalidLimit              = errors.New("invalid limit")
	errInvalidServerConnectivity = errors.New("invalid server_connectivity")
	errInvalidStale              = errors.New("invalid stale")
	errInvalidDerivedState       = errors.New("invalid derived_state")
)

// overviewQueryErrorCode returns the machine-readable error code for query
// validation errors. It is used by both the JSON and HTML error paths.
func overviewQueryErrorCode(err error) string {
	switch err {
	case errInvalidUpdatedSince:
		return "invalid_updated_since"
	case errInvalidLimit:
		return "invalid_limit"
	case errInvalidServerConnectivity:
		return "invalid_server_connectivity"
	case errInvalidStale:
		return "invalid_stale"
	case errInvalidDerivedState:
		return "invalid_derived_state"
	default:
		return "invalid_query"
	}
}

// overviewQueryErrorMessage returns the human-readable message for query
// validation errors. It is used by both the JSON and HTML error paths so
// that the wording stays consistent regardless of response format.
func overviewQueryErrorMessage(err error) string {
	switch err {
	case errInvalidUpdatedSince:
		return "updated_since ist kein gueltiger RFC3339-Zeitstempel"
	case errInvalidLimit:
		return "limit muss eine positive Ganzzahl sein"
	case errInvalidServerConnectivity:
		return "server_connectivity muss online, offline, degraded oder unknown sein"
	case errInvalidStale:
		return "stale muss true oder false sein"
	case errInvalidDerivedState:
		return "derived_state muss online, degraded oder offline sein"
	default:
		return "ungueltige Query-Parameter"
	}
}

func writeOverviewQueryError(w http.ResponseWriter, err error) {
	writeError(w, http.StatusBadRequest, overviewQueryErrorCode(err), overviewQueryErrorMessage(err), nil)
}

func validateOptionalRFC3339(value string) error {
	if strings.TrimSpace(value) == "" {
		return nil
	}

	_, err := time.Parse(time.RFC3339, value)
	return err
}

func parseOptionalRFC3339(value string) (*time.Time, error) {
	if strings.TrimSpace(value) == "" {
		return nil, nil
	}

	parsed, err := time.Parse(time.RFC3339, strings.TrimSpace(value))
	if err != nil {
		return nil, err
	}

	return &parsed, nil
}

func parseOptionalPositiveInt(value string) (int, error) {
	if strings.TrimSpace(value) == "" {
		return 0, nil
	}

	parsed, err := strconv.Atoi(strings.TrimSpace(value))
	if err != nil || parsed <= 0 {
		return 0, strconv.ErrSyntax
	}

	return parsed, nil
}

func isStale(record playerStatusRecord, now time.Time) bool {
	if strings.TrimSpace(record.ReceivedAt) == "" {
		return false
	}

	receivedAt, err := time.Parse(time.RFC3339, record.ReceivedAt)
	if err != nil {
		return false
	}

	threshold := staleThresholdFor(record)
	return now.Sub(receivedAt) > threshold
}

func staleThresholdFor(record playerStatusRecord) time.Duration {
	if record.HeartbeatEverySeconds > 0 {
		return time.Duration(record.HeartbeatEverySeconds*2) * time.Second
	}

	return 2 * time.Minute
}

func deriveState(record playerStatusRecord) string {
	if record.Stale || record.ServerConnectivity == "offline" {
		return "offline"
	}

	if record.ServerConnectivity == "degraded" || record.ServerConnectivity == "unknown" || record.Status != "running" {
		return "degraded"
	}

	return "online"
}

func isUpdatedSince(record playerStatusRecord, threshold time.Time) bool {
	if strings.TrimSpace(record.ReceivedAt) == "" {
		return false
	}

	receivedAt, err := time.Parse(time.RFC3339, record.ReceivedAt)
	if err != nil {
		return false
	}

	return !receivedAt.Before(threshold)
}

func derivedStatePriority(state string) int {
	switch state {
	case "offline":
		return 0
	case "degraded":
		return 1
	default:
		return 2
	}
}
