package api

import (
	"encoding/json"
	"errors"
	"net/http"
	"time"

	"github.com/go-chi/chi/v5"

	"git.fabledsword.com/bvandeusen/minstrel/internal/apierror"
	"git.fabledsword.com/bvandeusen/minstrel/internal/coverart"
)

// coverProviderResp is the wire shape for one provider in the
// GET /api/admin/cover-sources list. Mirrors coverart.ProviderInfo
// but uses snake_case JSON tags.
type coverProviderResp struct {
	ID             string   `json:"id"`
	DisplayName    string   `json:"display_name"`
	RequiresAPIKey bool     `json:"requires_api_key"`
	Supports       []string `json:"supports"`
	Enabled        bool     `json:"enabled"`
	APIKeySet      bool     `json:"api_key_set"`
	DisplayOrder   int32    `json:"display_order"`
	Testable       bool     `json:"testable"`
}

type coverProvidersListResp struct {
	Providers      []coverProviderResp `json:"providers"`
	SourcesVersion int32               `json:"sources_version"`
}

// handleListCoverSources implements GET /api/admin/cover-sources.
func (h *handlers) handleListCoverSources(w http.ResponseWriter, _ *http.Request) {
	infos := h.coverSettings.ListProviderInfo()
	out := coverProvidersListResp{
		Providers:      make([]coverProviderResp, 0, len(infos)),
		SourcesVersion: h.coverSettings.CurrentVersion(),
	}
	for _, info := range infos {
		supports := info.Supports
		if supports == nil {
			supports = []string{}
		}
		out.Providers = append(out.Providers, coverProviderResp{
			ID:             info.ID,
			DisplayName:    info.DisplayName,
			RequiresAPIKey: info.RequiresAPIKey,
			Supports:       supports,
			Enabled:        info.Enabled,
			APIKeySet:      info.APIKeySet,
			DisplayOrder:   info.DisplayOrder,
			Testable:       info.Testable,
		})
	}
	writeJSON(w, http.StatusOK, out)
}

// updateCoverSourceReq is the PATCH body. nil pointers mean
// "leave unchanged"; non-nil means "set to this value (which may be
// empty string for api_key clearing)".
type updateCoverSourceReq struct {
	Enabled *bool   `json:"enabled,omitempty"`
	APIKey  *string `json:"api_key,omitempty"`
}

type updateCoverSourceResp struct {
	coverProviderResp
	VersionBumped bool `json:"version_bumped"`
}

// handleUpdateCoverSource implements PATCH /api/admin/cover-sources/{provider_id}.
func (h *handlers) handleUpdateCoverSource(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "provider_id")

	var body updateCoverSourceReq
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		writeErr(w, apierror.BadRequest("bad_body", "invalid JSON"))
		return
	}

	bumped, err := h.coverSettings.UpdateProvider(r.Context(), id, coverart.ProviderUpdatePatch{
		Enabled: body.Enabled,
		APIKey:  body.APIKey,
	})
	if err != nil {
		if errors.Is(err, coverart.ErrProviderNotFound) {
			writeErr(w, &apierror.Error{Status: http.StatusNotFound, Code: "not_found", Message: "no such provider"})
			return
		}
		h.logger.Error("admin: update cover source", "id", id, "err", err)
		writeErr(w, apierror.InternalMsg("update failed", err))
		return
	}

	// Re-fetch the provider info to return.
	infos := h.coverSettings.ListProviderInfo()
	var found coverProviderResp
	for _, info := range infos {
		if info.ID == id {
			supports := info.Supports
			if supports == nil {
				supports = []string{}
			}
			found = coverProviderResp{
				ID:             info.ID,
				DisplayName:    info.DisplayName,
				RequiresAPIKey: info.RequiresAPIKey,
				Supports:       supports,
				Enabled:        info.Enabled,
				APIKeySet:      info.APIKeySet,
				DisplayOrder:   info.DisplayOrder,
				Testable:       info.Testable,
			}
			break
		}
	}
	writeJSON(w, http.StatusOK, updateCoverSourceResp{
		coverProviderResp: found,
		VersionBumped:     bumped,
	})
}

type testCoverSourceResp struct {
	OK         bool   `json:"ok"`
	DurationMs int64  `json:"duration_ms,omitempty"`
	Error      string `json:"error,omitempty"`
}

// handleTestCoverSource implements POST /api/admin/cover-sources/{provider_id}/test.
func (h *handlers) handleTestCoverSource(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "provider_id")

	start := time.Now()
	err := h.coverSettings.TestProvider(r.Context(), id)
	duration := time.Since(start).Milliseconds()

	if err == nil {
		writeJSON(w, http.StatusOK, testCoverSourceResp{OK: true, DurationMs: duration})
		return
	}

	if errors.Is(err, coverart.ErrProviderNotFound) {
		writeErr(w, &apierror.Error{Status: http.StatusNotFound, Code: "not_found", Message: "no such provider"})
		return
	}

	// Not testable, or test failed — both surface as ok=false.
	// 200 status because the operation completed; the test result is
	// data, not an error.
	writeJSON(w, http.StatusOK, testCoverSourceResp{
		OK:         false,
		DurationMs: duration,
		Error:      err.Error(),
	})
}
