package listing

import (
	"context"
	"fmt"
	"strings"
	"time"

	"gorm.io/datatypes"
	"gorm.io/gorm"
)

func (r *Repository) ListPublic(ctx context.Context, query PublicListQuery) (*PublicListResult, error) {
	page, pageSize := normalizedPublicPage(query)
	if canListPublicWithSQL(query) {
		return r.listPublicPage(ctx, query, page, pageSize)
	}

	var rows []listingRow
	err := r.baseQuery(ctx).
		Where("l.status = ? AND l.review_status = ? AND l.in_transaction = ?", "published", "approved", false).
		Order("l.published_at DESC, l.id DESC").
		Scan(&rows).Error
	if err != nil {
		return nil, err
	}
	items := publicListings(rowsToDTO(rows))
	baseQuery := query
	baseQuery.Zone = ""
	items = filterPublicListings(items, baseQuery)
	zoneCounts := publicZoneCounts(items)
	if query.Zone != "" && query.Zone != "all" {
		items = filterPublicListings(items, query)
	}
	sortPublicListings(items, query.Sort)
	total := int64(len(items))
	start := (page - 1) * pageSize
	if start < 0 {
		start = 0
	}
	if start >= len(items) {
		items = []ListingDTO{}
	} else {
		end := start + pageSize
		if end > len(items) {
			end = len(items)
		}
		items = items[start:end]
	}
	return &PublicListResult{
		Items:      items,
		Total:      total,
		Page:       page,
		PageSize:   pageSize,
		ZoneCounts: zoneCounts,
	}, nil
}

func (r *Repository) listPublicPage(ctx context.Context, query PublicListQuery, page int, pageSize int) (*PublicListResult, error) {
	var total int64
	if err := r.db.WithContext(ctx).Table("rental_listings AS l").
		Where("l.status = ? AND l.review_status = ? AND l.in_transaction = ?", "published", "approved", false).
		Count(&total).Error; err != nil {
		return nil, err
	}

	var rows []listingRow
	offset := (page - 1) * pageSize
	err := applyPublicSQLSort(r.baseQuery(ctx), query.Sort).
		Where("l.status = ? AND l.review_status = ? AND l.in_transaction = ?", "published", "approved", false).
		Limit(pageSize).
		Offset(offset).
		Scan(&rows).Error
	if err != nil {
		return nil, err
	}
	zoneCounts, err := r.publicZoneCountsCached(ctx)
	if err != nil {
		return nil, err
	}
	return &PublicListResult{
		Items:      publicListings(rowsToDTO(rows)),
		Total:      total,
		Page:       page,
		PageSize:   pageSize,
		ZoneCounts: zoneCounts,
	}, nil
}

func normalizedPublicPage(query PublicListQuery) (int, int) {
	page := query.Page
	if page <= 0 {
		page = 1
	}
	pageSize := query.PageSize
	if pageSize <= 0 {
		pageSize = 20
	}
	if pageSize > 50 {
		pageSize = 50
	}
	return page, pageSize
}

func canListPublicWithSQL(query PublicListQuery) bool {
	if query.Keyword != "" {
		return false
	}
	if query.Zone != "" && query.Zone != "all" {
		return false
	}
	if len(query.Server) > 0 || len(query.Region) > 0 || len(query.LoginMethod) > 0 || len(query.Rank) > 0 {
		return false
	}
	if len(query.Insurance) > 0 || len(query.Stamina) > 0 || len(query.Load) > 0 {
		return false
	}
	if len(query.SkinGroup) > 0 || len(query.SkinName) > 0 || len(query.ResourceRanges) > 0 {
		return false
	}
	if query.MinCoin != nil || query.MaxCoin != nil || query.MinRatio != nil || query.MaxRatio != nil {
		return false
	}
	if query.MinPrice != nil || query.MaxPrice != nil {
		return false
	}
	if query.MinDeposit != nil || query.MaxDeposit != nil || query.MinTotal != nil || query.MaxTotal != nil {
		return false
	}
	if query.MinFireLevel != nil || query.MaxFireLevel != nil || query.MinSecretKD != nil || query.MaxSecretKD != nil {
		return false
	}
	switch query.Sort {
	case "", "published", "recommended", "comprehensive", "priceAsc", "priceDesc", "coinDesc":
		return true
	default:
		return false
	}
}

func applyPublicSQLSort(db *gorm.DB, sortKey string) *gorm.DB {
	switch sortKey {
	case "priceAsc":
		return db.Order("l.price_cent ASC, l.published_at DESC, l.id DESC")
	case "priceDesc":
		return db.Order("l.price_cent DESC, l.published_at DESC, l.id DESC")
	case "coinDesc":
		return db.Order("a.haf_coin_amount DESC, l.published_at DESC, l.id DESC")
	case "published":
		return db.Order("l.published_at DESC, l.id DESC")
	default:
		return db.Order(fmt.Sprintf("((l.id * 1103515245 + %d) %% 2147483647) ASC, l.id ASC", publicShuffleSeed()))
	}
}

func publicShuffleSeed() int64 {
	now := time.Now().UTC()
	return int64(now.Year()*366 + now.YearDay())
}

func (r *Repository) publicZoneCountsCached(ctx context.Context) (map[string]int64, error) {
	now := time.Now()
	r.publicZoneCountsMu.Lock()
	defer r.publicZoneCountsMu.Unlock()
	if r.publicZoneCounts.Counts != nil && now.Before(r.publicZoneCounts.ExpiresAt) {
		return copyPublicZoneCounts(r.publicZoneCounts.Counts), nil
	}

	var rows []publicZoneRow
	err := r.db.WithContext(ctx).Table("rental_listings AS l").
		Select("a.login_platform, a.haf_coin_amount, a.asset_summary").
		Joins("JOIN game_accounts AS a ON a.id = l.account_id").
		Where("l.status = ? AND l.review_status = ? AND l.in_transaction = ?", "published", "approved", false).
		Scan(&rows).Error
	if err != nil {
		return nil, err
	}
	counts := map[string]int64{
		"all":      int64(len(rows)),
		"sale":     0,
		"gift":     0,
		"night":    0,
		"password": 0,
		"highCoin": 0,
	}
	for _, row := range rows {
		summary := decodeAssetSummary(row.AssetSummary)
		if isAcceleratedSale(summary) {
			counts["sale"]++
		}
		if hasGiftResourcesSummary(summary) {
			counts["gift"]++
		}
		if isNightAvailableSummary(summary) {
			counts["night"]++
		}
		if strings.Contains(row.LoginPlatform, "账密") || strings.Contains(row.LoginPlatform, "账号密码") {
			counts["password"]++
		}
		if float64(row.HafCoinAmount)/1000000 >= 100 {
			counts["highCoin"]++
		}
	}
	r.publicZoneCounts = publicZoneCountCache{
		Counts:    counts,
		ExpiresAt: now.Add(publicZoneCountCacheTTL),
	}
	return copyPublicZoneCounts(counts), nil
}

func copyPublicZoneCounts(counts map[string]int64) map[string]int64 {
	copied := make(map[string]int64, len(counts))
	for key, value := range counts {
		copied[key] = value
	}
	return copied
}

func (r *Repository) FindPublic(ctx context.Context, id uint64) (*ListingDTO, error) {
	dto, err := r.findDTO(ctx, "l.id = ? AND l.status = ? AND l.review_status = ? AND l.in_transaction = ?", id, "published", "approved", false)
	if err != nil {
		return nil, err
	}
	applyPublicListingURLs(dto)
	return dto, nil
}

func (r *Repository) FindPublicCoverKey(ctx context.Context, id uint64) (string, error) {
	return r.FindPublicScreenshotKey(ctx, id, 0)
}

func (r *Repository) FindPublicScreenshotKey(ctx context.Context, id uint64, index int) (string, error) {
	if index < 0 {
		return "", gorm.ErrRecordNotFound
	}
	dto, err := r.findDTO(ctx, "l.id = ? AND l.status = ? AND l.review_status = ? AND l.in_transaction = ?", id, "published", "approved", false)
	if err != nil {
		return "", err
	}
	if index >= len(dto.ScreenshotURLS) {
		return "", gorm.ErrRecordNotFound
	}
	if key := extractListingObjectKey(dto.ScreenshotURLS[index]); key != "" {
		return key, nil
	}
	return "", gorm.ErrRecordNotFound
}

type publicZoneRow struct {
	LoginPlatform string
	HafCoinAmount int64
	AssetSummary  datatypes.JSON `gorm:"column:asset_summary"`
}
