package listing

import (
	"sort"
	"strconv"
	"strings"
	"time"
)

func filterPublicListings(items []ListingDTO, query PublicListQuery) []ListingDTO {
	filtered := make([]ListingDTO, 0, len(items))
	for _, item := range items {
		if !matchesPublicQuery(item, query) {
			continue
		}
		filtered = append(filtered, item)
	}
	return filtered
}

func matchesPublicQuery(item ListingDTO, query PublicListQuery) bool {
	if !matchesPublicZone(item, query.Zone) {
		return false
	}
	if keyword := strings.ToLower(strings.TrimSpace(query.Keyword)); keyword != "" && !strings.Contains(publicSearchText(item), keyword) {
		return false
	}
	if !matchesAny(query.Server, strings.TrimSpace(item.ServerRegion)) {
		return false
	}
	if len(query.Region) > 0 && !intersects(query.Region, assetRegionsFromSummary(item.AssetSummary)) {
		return false
	}
	if !matchesAny(query.LoginMethod, strings.TrimSpace(item.LoginPlatform)) {
		return false
	}
	if !matchesAny(query.Rank, strings.TrimSpace(item.RankLevel)) {
		return false
	}
	if !matchesAny(query.Insurance, readAssetString(item.AssetSummary, "season_insurance")) {
		return false
	}
	if !matchesAny(query.Stamina, readAssetString(item.AssetSummary, "stamina_level")) {
		return false
	}
	if !matchesAny(query.Load, readAssetString(item.AssetSummary, "load_level")) {
		return false
	}
	if len(query.SkinName) > 0 {
		if len(query.SkinGroup) > 0 {
			if !skinGroupsContainAny(item.AssetSummary, query.SkinGroup, query.SkinName) {
				return false
			}
		} else if !intersects(query.SkinName, skinNamesFromSummary(item.AssetSummary)) {
			return false
		}
	} else if len(query.SkinGroup) > 0 && !skinGroupsHaveAny(item.AssetSummary, query.SkinGroup) {
		return false
	}

	price := float64(item.PriceCent) / 100
	deposit := float64(item.DepositAmountCent) / 100
	total := price + deposit
	coinM := coinMFromListing(item)
	if !numberInRange(coinM, NumberRange{Min: query.MinCoin, Max: query.MaxCoin}) {
		return false
	}
	if !numberInRange(ratioFromListing(item), NumberRange{Min: query.MinRatio, Max: query.MaxRatio}) {
		return false
	}
	if !numberInRange(price, NumberRange{Min: query.MinPrice, Max: query.MaxPrice}) {
		return false
	}
	if !numberInRange(deposit, NumberRange{Min: query.MinDeposit, Max: query.MaxDeposit}) {
		return false
	}
	if !numberInRange(total, NumberRange{Min: query.MinTotal, Max: query.MaxTotal}) {
		return false
	}
	if !numberInRange(readSummaryNumber(item.AssetSummary["fire_level"]), NumberRange{Min: query.MinFireLevel, Max: query.MaxFireLevel}) {
		return false
	}
	if !numberInRange(readSummaryNumber(item.AssetSummary["secret_kd"]), NumberRange{Min: query.MinSecretKD, Max: query.MaxSecretKD}) {
		return false
	}
	for resourceKey, resourceRange := range query.ResourceRanges {
		if !numberInRange(resourceQuantity(item.AssetSummary, resourceKey), resourceRange) {
			return false
		}
	}
	return true
}

func sortPublicListings(items []ListingDTO, sortKey string) {
	sort.SliceStable(items, func(i, j int) bool {
		a := items[i]
		b := items[j]
		switch sortKey {
		case "priceAsc":
			return a.PriceCent < b.PriceCent
		case "priceDesc":
			return a.PriceCent > b.PriceCent
		case "coinDesc":
			return a.HafCoinAmount > b.HafCoinAmount
		case "awmDesc":
			aAmmo := resourceQuantity(a.AssetSummary, "awmAmmo")
			bAmmo := resourceQuantity(b.AssetSummary, "awmAmmo")
			if aAmmo != bAmmo {
				return aAmmo > bAmmo
			}
			return a.HafCoinAmount > b.HafCoinAmount
		case "published":
			return publicRecentLess(a, b)
		case "recommended", "comprehensive", "":
			return publicShuffleLess(a, b)
		default:
			return publicShuffleLess(a, b)
		}
	})
}

func publicRecentLess(a ListingDTO, b ListingDTO) bool {
	aTime := time.Time{}
	bTime := time.Time{}
	if a.PublishedAt != nil {
		aTime = *a.PublishedAt
	}
	if b.PublishedAt != nil {
		bTime = *b.PublishedAt
	}
	if !aTime.Equal(bTime) {
		return aTime.After(bTime)
	}
	return a.ID > b.ID
}

func publicShuffleLess(a ListingDTO, b ListingDTO) bool {
	aKey := publicShuffleKey(a.ID)
	bKey := publicShuffleKey(b.ID)
	if aKey != bKey {
		return aKey < bKey
	}
	return a.ID < b.ID
}

func publicShuffleKey(id uint64) uint64 {
	return (id*1103515245 + uint64(publicShuffleSeed())) % 2147483647
}

func matchesPublicZone(item ListingDTO, zone string) bool {
	switch zone {
	case "", "all":
		return true
	case "sale":
		return item.IsAccelerated
	case "gift":
		return hasGiftResourcesSummary(item.AssetSummary)
	case "night":
		return isNightAvailableSummary(item.AssetSummary)
	case "password":
		return strings.Contains(item.LoginPlatform, "账密") || strings.Contains(item.LoginPlatform, "账号密码")
	case "highCoin":
		return coinMFromListing(item) >= 100
	default:
		return true
	}
}

func publicZoneCounts(items []ListingDTO) map[string]int64 {
	counts := map[string]int64{
		"all":      int64(len(items)),
		"sale":     0,
		"gift":     0,
		"night":    0,
		"password": 0,
		"highCoin": 0,
	}
	for _, item := range items {
		for _, zone := range []string{"sale", "gift", "night", "password", "highCoin"} {
			if matchesPublicZone(item, zone) {
				counts[zone]++
			}
		}
	}
	return counts
}

func publicSearchText(item ListingDTO) string {
	parts := []string{
		item.ListingNo,
		strconv.FormatUint(item.ID, 10),
		strconv.FormatUint(item.AccountID, 10),
		item.Title,
		item.Description,
		item.RankLevel,
		item.ServerRegion,
		item.LoginPlatform,
	}
	parts = append(parts, assetRegionsFromSummary(item.AssetSummary)...)
	parts = append(parts, skinNamesFromSummary(item.AssetSummary)...)
	return strings.ToLower(strings.Join(parts, " "))
}

func matchesAny(options []string, value string) bool {
	if len(options) == 0 {
		return true
	}
	for _, option := range options {
		if option == value {
			return true
		}
	}
	return false
}

func intersects(options []string, values []string) bool {
	if len(options) == 0 {
		return true
	}
	valueSet := make(map[string]struct{}, len(values))
	for _, value := range values {
		valueSet[value] = struct{}{}
	}
	for _, option := range options {
		if _, ok := valueSet[option]; ok {
			return true
		}
	}
	return false
}

func numberInRange(value float64, numberRange NumberRange) bool {
	if numberRange.Min != nil && value < *numberRange.Min {
		return false
	}
	if numberRange.Max != nil && value > *numberRange.Max {
		return false
	}
	return true
}

func coinMFromListing(item ListingDTO) float64 {
	return float64(item.HafCoinAmount) / 1000000
}

func coinWanFromListing(item ListingDTO) float64 {
	return float64(item.HafCoinAmount) / 10000
}

func ratioFromListing(item ListingDTO) float64 {
	ratio := readSummaryNumber(item.AssetSummary["publish_ratio"])
	if ratio > 0 {
		return ratio
	}
	price := float64(item.PriceCent) / 100
	if price <= 0 {
		return 0
	}
	return coinWanFromListing(item) / price
}

func readAssetString(summary map[string]any, key string) string {
	if summary == nil {
		return ""
	}
	value, _ := summary[key].(string)
	return strings.TrimSpace(value)
}

func assetRegionsFromSummary(summary map[string]any) []string {
	if summary == nil {
		return nil
	}
	values, ok := summary["common_regions"].([]any)
	if !ok {
		return nil
	}
	result := make([]string, 0, len(values))
	for _, value := range values {
		text, ok := value.(string)
		if ok && strings.TrimSpace(text) != "" {
			result = append(result, strings.TrimSpace(text))
		}
	}
	return result
}

func skinNamesFromSummary(summary map[string]any) []string {
	groups := skinGroupsFromSummary(summary)
	result := make([]string, 0)
	for _, skins := range groups {
		result = append(result, skins...)
	}
	return result
}

func skinGroupsContainAny(summary map[string]any, groupKeys []string, skinNames []string) bool {
	groups := skinGroupsFromSummary(summary)
	for _, groupKey := range groupKeys {
		if intersects(skinNames, groups[groupKey]) {
			return true
		}
	}
	return false
}

func skinGroupsHaveAny(summary map[string]any, groupKeys []string) bool {
	groups := skinGroupsFromSummary(summary)
	for _, groupKey := range groupKeys {
		if len(groups[groupKey]) > 0 {
			return true
		}
	}
	return false
}

func skinGroupsFromSummary(summary map[string]any) map[string][]string {
	result := make(map[string][]string)
	if summary == nil {
		return result
	}
	rawGroups, ok := summary["skin_groups"].(map[string]any)
	if !ok {
		return result
	}
	for key, rawSkins := range rawGroups {
		values, ok := rawSkins.([]any)
		if !ok {
			continue
		}
		for _, value := range values {
			text, ok := value.(string)
			if ok && strings.TrimSpace(text) != "" {
				result[key] = append(result[key], strings.TrimSpace(text))
			}
		}
	}
	return result
}

func resourceQuantity(summary map[string]any, resourceKey string) float64 {
	if summary == nil {
		return 0
	}
	resources, ok := summary["resources"].([]any)
	if !ok {
		return 0
	}
	for _, resource := range resources {
		row, ok := resource.(map[string]any)
		if !ok {
			continue
		}
		if rowKey, _ := row["key"].(string); rowKey == resourceKey {
			return readSummaryNumber(row["quantity"])
		}
	}
	return 0
}

func hasGiftResourcesSummary(summary map[string]any) bool {
	if summary == nil {
		return false
	}
	resources, ok := summary["resources"].([]any)
	if !ok {
		return false
	}
	for _, resource := range resources {
		row, ok := resource.(map[string]any)
		if !ok {
			continue
		}
		if mode, _ := row["mode"].(string); mode == "赠送" && readSummaryNumber(row["quantity"]) > 0 {
			return true
		}
	}
	return false
}

func isNightAvailableSummary(summary map[string]any) bool {
	if summary == nil {
		return false
	}
	onlineTime, ok := summary["online_time"].(map[string]any)
	if !ok {
		return false
	}
	start, okStart := parseTimeHourValue(onlineTime["start"])
	end, okEnd := parseTimeHourValue(onlineTime["end"])
	if !okStart || !okEnd {
		return false
	}
	return timeRangeCoversHour(start, end, 22) || timeRangeCoversHour(start, end, 23) || timeRangeCoversHour(start, end, 0)
}

func parseTimeHourValue(value any) (int, bool) {
	text, ok := value.(string)
	if !ok {
		return 0, false
	}
	parts := strings.Split(text, ":")
	hour, err := strconv.Atoi(parts[0])
	if err != nil || hour < 0 || hour > 23 {
		return 0, false
	}
	return hour, true
}

func timeRangeCoversHour(start int, end int, hour int) bool {
	if start == end {
		return true
	}
	if start < end {
		return hour >= start && hour <= end
	}
	return hour >= start || hour <= end
}
