package recommendations

import (
	"mal/integrations/jikan"
	"mal/internal/db"
	"math"
	"sort"
	"strings"
	"time"
)

func buildRecommendationSeeds(now time.Time, watchlist []db.GetUserWatchListRow) []recommendationSeed {
	seeds := make([]recommendationSeed, 0, min(len(watchlist), maxSeeds))

	for _, entry := range watchlist {
		weight := recommendationEntryWeight(now, entry)
		if weight <= 0 || entry.AnimeID <= 0 {
			continue
		}

		seeds = append(seeds, recommendationSeed{
			animeID: int(entry.AnimeID),
			weight:  weight,
		})
		if len(seeds) >= maxSeeds {
			break
		}
	}

	return seeds
}

func recommendationEntryWeight(now time.Time, entry db.GetUserWatchListRow) float64 {
	status := strings.TrimSpace(entry.Status)

	var statusWeight float64
	switch status {
	case "completed":
		statusWeight = 1.0
	case "watching":
		statusWeight = 0.9
	case "plan_to_watch":
		statusWeight = 0.35
	default:
		return 0
	}

	recencyWeight := 1.0
	if !entry.UpdatedAt.IsZero() {
		age := now.Sub(entry.UpdatedAt)
		if age > 0 {
			recencyWeight = math.Max(0.35, 1-(age.Hours()/seedRecencyWindow.Hours()))
		}
	}

	progressWeight := 0.6
	if entry.CurrentEpisode.Valid && entry.CurrentEpisode.Int64 > 0 {
		progressWeight = min(1.0, 0.6+(0.08*float64(entry.CurrentEpisode.Int64)))
	}

	return statusWeight * recencyWeight * progressWeight
}

func buildTasteProfile(now time.Time, seeds []recommendationSeed, seedAnimes []jikan.Anime) userTasteProfile {
	profile := userTasteProfile{
		genres:       make(map[int]float64),
		themes:       make(map[int]float64),
		studios:      make(map[int]float64),
		demographics: make(map[int]float64),
	}

	var totalWeight float64
	var airingWeight float64
	var recentWeight float64

	for i, anime := range seedAnimes {
		seedWeight := 1.0
		if i < len(seeds) && seeds[i].weight > 0 {
			seedWeight = seeds[i].weight
		}

		addEntityWeights(profile.genres, anime.Genres, seedWeight)
		addEntityWeights(profile.themes, anime.Themes, seedWeight*0.7)
		addEntityWeights(profile.studios, anime.Studios, seedWeight*0.5)
		addEntityWeights(profile.demographics, anime.Demographics, seedWeight*0.7)

		if anime.Airing {
			airingWeight += seedWeight
		}
		if anime.Year > 0 && now.Year()-anime.Year <= 4 {
			recentWeight += seedWeight
		}
		totalWeight += seedWeight
	}

	if totalWeight > 0 {
		profile.prefersAiring = airingWeight/totalWeight >= 0.5
		profile.prefersRecent = recentWeight/totalWeight >= 0.5
	}

	return profile
}

func addEntityWeights(target map[int]float64, entities []jikan.NamedEntity, weight float64) {
	for _, entity := range entities {
		if entity.MalID <= 0 {
			continue
		}
		target[entity.MalID] += weight
	}
}

func buildProfileSearchQueries(profile userTasteProfile) []profileSearchQuery {
	queries := make([]profileSearchQuery, 0, 6)

	for _, entity := range strongestWeightedEntities(profile.genres, profileGenreSearches) {
		queries = append(queries, profileSearchQuery{
			genreIDs: []int{entity.id},
			weight:   entity.weight,
		})
	}

	for _, entity := range strongestWeightedEntities(profile.themes, profileThemeSearches) {
		queries = append(queries, profileSearchQuery{
			genreIDs: []int{entity.id},
			weight:   entity.weight * 0.8,
		})
	}

	for _, entity := range strongestWeightedEntities(profile.demographics, 1) {
		queries = append(queries, profileSearchQuery{
			genreIDs: []int{entity.id},
			weight:   entity.weight * 0.8,
		})
	}

	for _, entity := range strongestWeightedEntities(profile.studios, 1) {
		queries = append(queries, profileSearchQuery{
			studioID: entity.id,
			weight:   entity.weight * 0.7,
		})
	}

	return queries
}

func strongestWeightedEntities(weights map[int]float64, limit int) []weightedEntity {
	if limit <= 0 || len(weights) == 0 {
		return []weightedEntity{}
	}

	items := make([]weightedEntity, 0, len(weights))
	for id, weight := range weights {
		if id <= 0 || weight <= 0 {
			continue
		}
		items = append(items, weightedEntity{id: id, weight: weight})
	}

	sort.Slice(items, func(i, j int) bool {
		if items[i].weight == items[j].weight {
			return items[i].id < items[j].id
		}
		return items[i].weight > items[j].weight
	})

	if len(items) > limit {
		return items[:limit]
	}
	return items
}
