package api

import (
	"context"
	"errors"
	"net/http"
	"time"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgtype"

	"git.fabledsword.com/bvandeusen/minstrel/internal/apierror"
	"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)

// LBStatus is the GET response body and the PUT response body. Token is
// write-only — `token_set` is the only signal back to the client about
// whether a token exists.
type LBStatus struct {
	Enabled         bool    `json:"enabled"`
	TokenSet        bool    `json:"token_set"`
	LastScrobbledAt *string `json:"last_scrobbled_at"`
}

func (h *handlers) handleGetListenBrainz(w http.ResponseWriter, r *http.Request) {
	user, ok := requireUser(w, r)
	if !ok {
		return
	}
	resp, err := h.buildLBStatus(r.Context(), user.ID)
	if err != nil {
		h.logger.Error("api: get listenbrainz", "err", err)
		writeErr(w, apierror.InternalMsg("lookup failed", err))
		return
	}
	writeJSON(w, http.StatusOK, resp)
}

type putLBBody struct {
	Token   *string `json:"token,omitempty"`
	Enabled *bool   `json:"enabled,omitempty"`
}

func (h *handlers) handlePutListenBrainz(w http.ResponseWriter, r *http.Request) {
	user, ok := requireUser(w, r)
	if !ok {
		return
	}
	var body putLBBody
	if !decodeBody(w, r, &body) {
		return
	}

	q := dbq.New(h.pool)
	if body.Token != nil {
		t := *body.Token
		var tokenPtr *string
		if t != "" {
			tokenPtr = &t
		}
		if err := q.SetListenBrainzToken(r.Context(), dbq.SetListenBrainzTokenParams{
			ID: user.ID, ListenbrainzToken: tokenPtr,
		}); err != nil {
			h.logger.Error("api: set listenbrainz token", "err", err)
			writeErr(w, apierror.InternalMsg("update failed", err))
			return
		}
	}
	if body.Enabled != nil && *body.Enabled {
		// Enabling requires a non-empty token. Re-read state since the
		// preceding token update may have just set/cleared it.
		cfg, err := q.GetListenBrainzConfig(r.Context(), user.ID)
		if err != nil {
			h.logger.Error("api: get listenbrainz config (pre-enable)", "err", err)
			writeErr(w, apierror.InternalMsg("lookup failed", err))
			return
		}
		if cfg.ListenbrainzToken == nil || *cfg.ListenbrainzToken == "" {
			writeErr(w, apierror.BadRequest("bad_request", "cannot enable without a token"))
			return
		}
	}
	if body.Enabled != nil {
		if err := q.SetListenBrainzEnabled(r.Context(), dbq.SetListenBrainzEnabledParams{
			ID: user.ID, ListenbrainzEnabled: *body.Enabled,
		}); err != nil {
			h.logger.Error("api: set listenbrainz enabled", "err", err)
			writeErr(w, apierror.InternalMsg("update failed", err))
			return
		}
	}

	resp, err := h.buildLBStatus(r.Context(), user.ID)
	if err != nil {
		h.logger.Error("api: put listenbrainz refresh status", "err", err)
		writeErr(w, apierror.InternalMsg("lookup failed", err))
		return
	}
	writeJSON(w, http.StatusOK, resp)
}

func (h *handlers) buildLBStatus(ctx context.Context, userID pgtype.UUID) (LBStatus, error) {
	q := dbq.New(h.pool)
	cfg, err := q.GetListenBrainzConfig(ctx, userID)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return LBStatus{}, nil
		}
		return LBStatus{}, err
	}
	out := LBStatus{
		Enabled:  cfg.ListenbrainzEnabled,
		TokenSet: cfg.ListenbrainzToken != nil && *cfg.ListenbrainzToken != "",
	}
	if cfg.LastScrobbledAt.Valid {
		s := cfg.LastScrobbledAt.Time.UTC().Format(time.RFC3339)
		out.LastScrobbledAt = &s
	}
	return out, nil
}
