package api

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

	"github.com/ponzischeme89/memby/server/internal/recommend"
	"github.com/ponzischeme89/memby/server/internal/store"
)

type adminRecommendationItem struct {
	ID                    string                     `json:"id"`
	Title                 string                     `json:"title"`
	Type                  string                     `json:"type"`
	Year                  int                        `json:"year,omitempty"`
	Genres                []string                   `json:"genres"`
	RuntimeMinutes        int                        `json:"runtimeMinutes"`
	BaseRank              int                        `json:"baseRank"`
	BaseScore             float64                    `json:"baseScore"`
	AffinityScore         float64                    `json:"affinityScore"`
	CompatibilityScore    float64                    `json:"compatibilityScore"`
	CompatibilityLabel    string                     `json:"compatibilityLabel,omitempty"`
	PreparedReason        string                     `json:"preparedReason,omitempty"`
	PreparedReasonKind    string                     `json:"preparedReasonKind,omitempty"`
	PreparedReasonGenre   string                     `json:"preparedReasonGenre,omitempty"`
	PreparedEvidenceTitle string                     `json:"preparedEvidenceTitle,omitempty"`
	EligibleRows          []string                   `json:"eligibleRows"`
	Exposure              recommend.ItemExposure     `json:"exposure"`
	Explanation           recommend.ScoreExplanation `json:"explanation"`
}

type adminRecommendationProfileMeta struct {
	SourceEvents     int        `json:"sourceEvents"`
	BuiltAt          *time.Time `json:"builtAt,omitempty"`
	PoolBuiltAt      *time.Time `json:"poolBuiltAt,omitempty"`
	DirtySince       *time.Time `json:"dirtySince,omitempty"`
	AlgorithmVersion string     `json:"algorithmVersion"`
}

type adminRecommendationAction struct {
	ItemID    string    `json:"itemId"`
	Title     string    `json:"title,omitempty"`
	Action    string    `json:"action"`
	UpdatedAt time.Time `json:"updatedAt"`
}

type adminRecommendationResponse struct {
	User               store.KnownUser                `json:"user"`
	Context            string                         `json:"context"`
	GeneratedAt        time.Time                      `json:"generatedAt"`
	Profile            recommend.WeightedProfile      `json:"profile"`
	ProfileMeta        adminRecommendationProfileMeta `json:"profileMeta"`
	Actions            []adminRecommendationAction    `json:"actions"`
	PoolCandidates     int                            `json:"poolCandidates"`
	PermissionEligible int                            `json:"permissionEligible"`
	Items              []adminRecommendationItem      `json:"items"`
}

func (s *Server) handleAdminRecommendations(w http.ResponseWriter, r *http.Request) {
	userID := strings.TrimSpace(r.URL.Query().Get("userId"))
	if userID == "" {
		writeError(w, http.StatusBadRequest, "userId is required")
		return
	}
	users, err := s.store.KnownUsers(r.Context())
	if err != nil {
		writeError(w, http.StatusInternalServerError, "could not load users")
		return
	}
	var user store.KnownUser
	found := false
	for _, value := range users {
		if value.ID == userID {
			user, found = value, true
			break
		}
	}
	if !found {
		writeError(w, http.StatusNotFound, "unknown user")
		return
	}

	sessions, err := s.store.ActiveRecommendationUsers(r.Context())
	if err != nil {
		writeError(w, http.StatusInternalServerError, "could not load user session")
		return
	}
	var session store.Session
	for _, value := range sessions {
		if value.EmbyUserID == userID {
			session = value
			break
		}
	}
	if session.EmbyUserID == "" {
		writeError(w, http.StatusConflict, "user has no active Emby session")
		return
	}

	prepared, _, err := s.store.PreparedForYou(r.Context(), userID, 0, 500)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "could not load recommendation pool")
		return
	}
	poolCount := len(prepared)
	permissionRow := recommend.Row{ID: "for-you:admin-pressure-test", Kind: "for-you"}
	for _, item := range prepared {
		permissionRow.Items = append(permissionRow.Items, item.Payload)
	}
	filtered := s.filterRecommendationPermissions(
		r.Context(), session, []recommend.Row{permissionRow},
	)
	allowed := map[string]bool{}
	if len(filtered) == 1 {
		for _, item := range recommend.Decode(filtered[0].Items) {
			allowed[item.ID] = true
		}
	}

	profile, exposures, household := s.rankingContext(r.Context(), userID)
	now := time.Now()
	if raw := strings.TrimSpace(r.URL.Query().Get("at")); raw != "" {
		if parsed, parseErr := time.Parse(time.RFC3339, raw); parseErr == nil {
			now = parsed
		}
	}
	contextName := strings.TrimSpace(r.URL.Query().Get("context"))
	if contextName == "" {
		contextName = "default"
	}
	intent := recommend.RankIntent{
		ID: "admin:" + contextName, Now: now, Location: s.cfg.SonarrLocation,
		HouseholdScores: household, Compatibility: map[string]float64{},
	}
	switch contextName {
	case "default":
	case "bedtime":
		intent.PreferShort, intent.MaxRuntimeMins = true, 60
	case "hidden":
		intent.HiddenLibrary, intent.UnseenOnly = true, true
	case "new-releases":
		intent.NewReleasesOnly = true
	default:
		writeError(w, http.StatusBadRequest, "unknown pressure-test context")
		return
	}
	if minutes := queryInt(r, "minutes", 0, 360); minutes > 0 {
		intent.MaxRuntimeMins = minutes
	}

	candidates := make([]recommend.Item, 0, len(prepared))
	byID := make(map[string]store.PreparedForYouItem, len(prepared))
	for _, item := range prepared {
		if !allowed[item.ItemID] {
			continue
		}
		decoded := recommend.Decode([]json.RawMessage{item.Payload})
		if len(decoded) != 1 {
			continue
		}
		candidates = append(candidates, decoded[0])
		byID[item.ItemID] = item
		intent.Compatibility[item.ItemID] = item.CompatibilityScore
	}
	limit := queryInt(r, "limit", 100, 200)
	ranked := recommend.WeightedRank(
		profile, candidates, exposures, intent, s.weightedConfig(), limit,
	)
	items := make([]adminRecommendationItem, 0, len(ranked))
	for _, value := range ranked {
		preparedItem := byID[value.Item.ID]
		items = append(items, adminRecommendationItem{
			ID: value.Item.ID, Title: value.Item.Name, Type: value.Item.Type,
			Year: value.Item.ProductionYear, Genres: value.Item.Genres,
			RuntimeMinutes: value.Item.RuntimeMinutes(),
			BaseRank:       preparedItem.BaseRank, BaseScore: preparedItem.BaseScore,
			AffinityScore:         preparedItem.AffinityScore,
			CompatibilityScore:    preparedItem.CompatibilityScore,
			CompatibilityLabel:    preparedItem.CompatibilityLabel,
			PreparedReason:        preparedItem.RecommendationReason,
			PreparedReasonKind:    preparedItem.ReasonKind,
			PreparedReasonGenre:   preparedItem.ReasonGenre,
			PreparedEvidenceTitle: preparedItem.ReasonSourceTitle,
			EligibleRows:          adminEligibleRows(preparedItem, value.Item),
			Exposure:              exposures[value.Item.ID], Explanation: value.Explanation,
		})
	}
	storedActions, _ := s.store.RecommendationActions(r.Context(), userID)
	actionIDs := make([]string, 0, len(storedActions))
	for _, action := range storedActions {
		actionIDs = append(actionIDs, action.ItemID)
	}
	actionTitles := map[string]string{}
	if raws, actionErr := s.store.LibraryItemsByID(r.Context(), actionIDs); actionErr == nil {
		for _, item := range recommend.Decode(raws) {
			actionTitles[item.ID] = item.Name
		}
	}
	actions := make([]adminRecommendationAction, 0, len(storedActions))
	for _, action := range storedActions {
		actions = append(actions, adminRecommendationAction{
			ItemID: action.ItemID, Title: actionTitles[action.ItemID],
			Action: action.Action, UpdatedAt: action.UpdatedAt,
		})
	}
	builtAt, poolBuiltAt, dirtySince, version, _ :=
		s.store.ForYouProfileTimes(r.Context(), userID)
	writeJSON(w, http.StatusOK, adminRecommendationResponse{
		User: user, Context: contextName, GeneratedAt: now, Profile: profile,
		ProfileMeta: adminRecommendationProfileMeta{
			SourceEvents: profile.SourceEvents, BuiltAt: builtAt,
			PoolBuiltAt: poolBuiltAt, DirtySince: dirtySince, AlgorithmVersion: version,
		},
		Actions: actions, PoolCandidates: poolCount,
		PermissionEligible: len(candidates), Items: items,
	})
}

func adminEligibleRows(item store.PreparedForYouItem, decoded recommend.Item) []string {
	rows := []string{"Top picks for you"}
	if item.ReasonKind == "pick-up" {
		rows = append(rows, "Pick this show up again")
	}
	if item.ReasonSourceTitle != "" {
		rows = append(rows, "Because you finished "+item.ReasonSourceTitle)
	}
	if item.ReasonGenre != "" {
		rows = append(rows, "More "+item.ReasonGenre+" for you")
	}
	if item.CompatibilityScore > 0.2 {
		rows = append(rows, "Plays well on this TV")
	}
	if decoded.Type == "Series" && item.RuntimeMinutes >= 15 && item.RuntimeMinutes <= 50 {
		rows = append(rows, "One episode before bed")
	}
	if item.ReasonKind != "pick-up" {
		rows = append(rows, "Hidden in your library")
	}
	return rows
}
