package jikan

import (
	"context"
	"encoding/json"
	"fmt"
	"math/rand"
	"time"
)

type ScheduleResult struct {
	Animes      []Anime
	HasNextPage bool // whether more pages available
}

// GetSeasonsNow returns currently airing anime for the current season.
func (c *Client) GetSeasonsNow(ctx context.Context, page int) (TopAnimeResult, error) {
	return c.getSeasonList(ctx, page, "now")
}

// GetSeasonsUpcoming returns anime scheduled to air in upcoming seasons.
func (c *Client) GetSeasonsUpcoming(ctx context.Context, page int) (TopAnimeResult, error) {
	return c.getSeasonList(ctx, page, "upcoming")
}

func (c *Client) getSeasonList(ctx context.Context, page int, season string) (TopAnimeResult, error) {
	if page < 1 {
		page = 1
	}
	cacheKey := fmt.Sprintf("seasons_%s:%d", season, page)

	var result TopAnimeResponse
	reqURL := fmt.Sprintf("%s/seasons/%s?page=%d", c.baseURL, season, page)

	err := c.getWithCache(ctx, cacheKey, shortCacheTTL, reqURL, &result)
	if err != nil {
		return TopAnimeResult{}, err
	}

	return TopAnimeResult{
		Animes:      result.Data,
		HasNextPage: result.Pagination.HasNextPage,
	}, nil
}

// seedRandomPool seeds the in-memory pool of random anime
func (c *Client) seedRandomPool(ctx context.Context) error {
	c.poolMu.Lock()
	if c.poolInitialized {
		c.poolMu.Unlock()
		return nil
	}
	c.poolInitialized = true
	c.poolMu.Unlock()

	// 1. Try to load all cached anime from the database
	cachedJSONs, err := c.db.GetAllCachedAnime(ctx)
	if err == nil && len(cachedJSONs) > 0 {
		var loadedAnimes []Anime
		for _, dataStr := range cachedJSONs {
			var anime Anime
			if err := json.Unmarshal([]byte(dataStr), &anime); err == nil && anime.MalID > 0 {
				loadedAnimes = append(loadedAnimes, anime)
			}
		}

		if len(loadedAnimes) > 0 {
			c.poolMu.Lock()
			c.randomPool = append(c.randomPool, loadedAnimes...)
			c.poolMu.Unlock()
		}
	}

	// 2. Fetch Top Anime page 1 & 2 to ensure we have a robust baseline of high-quality popular anime
	go func() {
		bgCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
		defer cancel()

		var fetchedAnimes []Anime

		top, err := c.GetTopAnime(bgCtx, 1)
		if err == nil && len(top.Animes) > 0 {
			fetchedAnimes = append(fetchedAnimes, top.Animes...)
		}

		top2, err := c.GetTopAnime(bgCtx, 2)
		if err == nil && len(top2.Animes) > 0 {
			fetchedAnimes = append(fetchedAnimes, top2.Animes...)
		}

		now, err := c.GetSeasonsNow(bgCtx, 1)
		if err == nil && len(now.Animes) > 0 {
			fetchedAnimes = append(fetchedAnimes, now.Animes...)
		}

		if len(fetchedAnimes) > 0 {
			c.poolMu.Lock()
			// Use map to de-duplicate any anime
			seen := make(map[int]bool)
			for _, a := range c.randomPool {
				seen[a.MalID] = true
			}
			for _, a := range fetchedAnimes {
				if !seen[a.MalID] {
					c.randomPool = append(c.randomPool, a)
					seen[a.MalID] = true
				}
			}
			c.poolMu.Unlock()
		}

		// Start background refresher once seeding completes
		c.startPoolRefresher()
	}()

	return nil
}

// startPoolRefresher runs in the background to slowly mix in true random anime
func (c *Client) startPoolRefresher() {
	ticker := time.NewTicker(30 * time.Second)
	ctx := context.Background()

	for range ticker.C {
		var result struct {
			Data Anime `json:"data"`
		}
		reqURL := fmt.Sprintf("%s/random/anime", c.baseURL)
		err := c.fetchWithRetry(ctx, reqURL, &result)
		if err != nil {
			continue
		}

		if result.Data.MalID == 0 {
			continue
		}

		c.poolMu.Lock()
		if len(c.randomPool) >= 1000 {
			idx := rand.Intn(len(c.randomPool))
			c.randomPool[idx] = result.Data
		} else {
			duplicate := false
			for _, a := range c.randomPool {
				if a.MalID == result.Data.MalID {
					duplicate = true
					break
				}
			}
			if !duplicate {
				c.randomPool = append(c.randomPool, result.Data)
			}
		}
		c.poolMu.Unlock()
	}
}

// GetRandomAnime returns a random anime from the database.
func (c *Client) GetRandomAnime(ctx context.Context) (Anime, error) {
	c.poolMu.Lock()
	initialized := c.poolInitialized
	c.poolMu.Unlock()

	if !initialized {
		_ = c.seedRandomPool(ctx)
	}

	c.poolMu.RLock()
	defer c.poolMu.RUnlock()

	if len(c.randomPool) == 0 {
		var result struct {
			Data Anime `json:"data"`
		}

		reqURL := fmt.Sprintf("%s/random/anime", c.baseURL)
		err := c.fetchWithRetry(ctx, reqURL, &result)
		if err != nil {
			return Anime{}, err
		}
		if result.Data.MalID == 0 {
			return Anime{}, fmt.Errorf("jikan: empty response for random/anime")
		}

		return result.Data, nil
	}

	idx := rand.Intn(len(c.randomPool))
	return c.randomPool[idx], nil
}
