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/tags"
)

// tagSourceResp is the wire shape for one tag-enrichment provider in the
// GET /api/admin/tag-sources list. Mirrors the cover-sources admin surface
// (admin_cover_sources.go) — a separate, independent settings card so a new
// tag source is added without touching art settings (#1490).
type tagSourceResp 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"`
}

func tagSourceRespFrom(info tags.ProviderInfo) tagSourceResp {
	supports := info.Supports
	if supports == nil {
		supports = []string{}
	}
	return tagSourceResp{
		ID:             info.ID,
		DisplayName:    info.DisplayName,
		RequiresAPIKey: info.RequiresAPIKey,
		Supports:       supports,
		Enabled:        info.Enabled,
		APIKeySet:      info.APIKeySet,
		DisplayOrder:   info.DisplayOrder,
		Testable:       info.Testable,
	}
}

type tagSourcesListResp struct {
	Providers      []tagSourceResp `json:"providers"`
	SourcesVersion int32           `json:"sources_version"`
}

// handleListTagSources implements GET /api/admin/tag-sources.
func (h *handlers) handleListTagSources(w http.ResponseWriter, _ *http.Request) {
	infos := h.tagSettings.ListProviderInfo()
	out := tagSourcesListResp{
		Providers:      make([]tagSourceResp, 0, len(infos)),
		SourcesVersion: h.tagSettings.CurrentVersion(),
	}
	for _, info := range infos {
		out.Providers = append(out.Providers, tagSourceRespFrom(info))
	}
	writeJSON(w, http.StatusOK, out)
}

// updateTagSourceReq is the PATCH body. nil pointers mean "leave
// unchanged"; non-nil sets (empty api_key clears).
type updateTagSourceReq struct {
	Enabled *bool   `json:"enabled,omitempty"`
	APIKey  *string `json:"api_key,omitempty"`
}

type updateTagSourceResp struct {
	tagSourceResp
	VersionBumped bool `json:"version_bumped"`
}

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

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

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

	var found tagSourceResp
	for _, info := range h.tagSettings.ListProviderInfo() {
		if info.ID == id {
			found = tagSourceRespFrom(info)
			break
		}
	}
	writeJSON(w, http.StatusOK, updateTagSourceResp{tagSourceResp: found, VersionBumped: bumped})
}

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

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

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

	if err == nil {
		writeJSON(w, http.StatusOK, testTagSourceResp{OK: true, DurationMs: duration})
		return
	}
	if errors.Is(err, tags.ErrProviderNotFound) {
		writeErr(w, &apierror.Error{Status: http.StatusNotFound, Code: "not_found", Message: "no such provider"})
		return
	}
	// Not testable, or the test failed — both surface as ok=false with a
	// 200 (the operation completed; the result is data, not an error).
	writeJSON(w, http.StatusOK, testTagSourceResp{OK: false, DurationMs: duration, Error: err.Error()})
}

type researchTagsResp struct {
	SourcesVersion int32 `json:"sources_version"`
}

// handleResearchTags implements POST /api/admin/tag-sources/research: bump
// the sources version so every settled ('none') track becomes eligible for
// a re-enrichment pass, then the background worker re-processes them.
func (h *handlers) handleResearchTags(w http.ResponseWriter, r *http.Request) {
	newVer, err := h.tagSettings.BumpVersion(r.Context())
	if err != nil {
		h.logger.Error("admin: research tags", "err", err)
		writeErr(w, apierror.InternalMsg("research failed", err))
		return
	}
	writeJSON(w, http.StatusOK, researchTagsResp{SourcesVersion: newVer})
}
