package listing

import (
	"encoding/json"
	"math"
	"net/url"
	"strconv"
	"strings"

	"hfb_sys/backend/internal/model"
	"hfb_sys/backend/pkg/money"

	"gorm.io/datatypes"
)

type listingRow struct {
	model.RentalListing
	Title          string
	OwnerPhone     string
	OwnerNickname  string
	Description    string
	GameName       string
	ServerRegion   string
	LoginPlatform  string
	RankLevel      string
	HafCoinAmount  int64
	AssetSummary   datatypes.JSON `gorm:"column:asset_summary"`
	ScreenshotURLS datatypes.JSON `gorm:"column:screenshot_urls"`
}

func rowsToDTO(rows []listingRow) []ListingDTO {
	items := make([]ListingDTO, 0, len(rows))
	for _, row := range rows {
		items = append(items, row.toDTO())
	}
	return items
}

func normalizedListingPriceCent(req CreateRequest) int64 {
	if req.AssetSummary != nil {
		if breakdown, ok := req.AssetSummary["price_breakdown"].(map[string]any); ok {
			buyerPrice := readSummaryNumber(breakdown["buyer_total_price"])
			if buyerPrice > 0 {
				return yuanToCent(buyerPrice)
			}
		}
	}
	return req.PriceCent
}

func publicListings(items []ListingDTO) []ListingDTO {
	for index := range items {
		applyPublicListingURLs(&items[index])
	}
	return items
}

func sellerListings(items []ListingDTO) []ListingDTO {
	for index := range items {
		applySellerListingPrice(&items[index])
	}
	return items
}

func applyPublicListingURLs(item *ListingDTO) {
	item.ScreenshotURLS = publicScreenshotURLs(item.ID, item.ScreenshotURLS, item.Status, item.ReviewStatus)
	if item.AssetSummary != nil {
		delete(item.AssetSummary, "price_breakdown")
	}
}

func applySellerListingPrice(item *ListingDTO) {
	if item == nil || item.AssetSummary == nil {
		return
	}
	breakdown, ok := item.AssetSummary["price_breakdown"].(map[string]any)
	if !ok {
		return
	}
	sellerPrice := readSummaryNumber(breakdown["seller_total_price"])
	if sellerPrice > 0 {
		item.PriceCent = int64(math.Round(sellerPrice * 100))
	}
	sellerRatio := readSummaryNumber(breakdown["seller_ratio"])
	if sellerRatio > 0 {
		item.AssetSummary["publish_ratio"] = sellerRatio
	}
	delete(breakdown, "buyer_coin_base_price")
	delete(breakdown, "buyer_total_price")
	delete(breakdown, "buyer_ratio")
	delete(breakdown, "platform_markup_amount")
	delete(breakdown, "platform_rule_type")
}

func (row listingRow) toDTO() ListingDTO {
	assetSummary := decodeAssetSummary(row.AssetSummary)
	screenshotURLS := cleanScreenshotURLs(decodeScreenshots(row.ScreenshotURLS))
	reviewStatus, reviewReason := normalizedReviewState(row.Status, row.ReviewStatus, row.ReviewReason)
	return ListingDTO{
		ID:                row.ID,
		ListingNo:         row.ListingNo,
		AccountID:         row.AccountID,
		OwnerID:           row.OwnerID,
		OwnerPhone:        row.OwnerPhone,
		OwnerNickname:     row.OwnerNickname,
		Title:             row.Title,
		Description:       row.Description,
		GameName:          row.GameName,
		ServerRegion:      row.ServerRegion,
		LoginPlatform:     row.LoginPlatform,
		RankLevel:         row.RankLevel,
		HafCoinAmount:     row.HafCoinAmount,
		AssetSummary:      assetSummary,
		ScreenshotURLS:    screenshotURLS,
		CoverURL:          publicCoverURL(row.ID, screenshotURLS, row.Status, row.ReviewStatus),
		PriceCent:         row.PriceCent,
		DepositAmountCent: row.DepositAmountCent,
		IsAccelerated:     isAcceleratedSale(assetSummary),
		InTransaction:     row.InTransaction,
		Status:            row.Status,
		ReviewStatus:      reviewStatus,
		ReviewReason:      reviewReason,
		PublishedAt:       row.PublishedAt,
		CreatedAt:         row.CreatedAt,
		UpdatedAt:         row.UpdatedAt,
	}
}

func toDTO(account model.GameAccount, listing model.RentalListing) *ListingDTO {
	assetSummary := decodeAssetSummary(account.AssetSummary)
	screenshotURLS := cleanScreenshotURLs(decodeScreenshots(account.ScreenshotURLS))
	reviewStatus, reviewReason := normalizedReviewState(listing.Status, listing.ReviewStatus, listing.ReviewReason)
	return &ListingDTO{
		ID:                listing.ID,
		ListingNo:         listing.ListingNo,
		AccountID:         account.ID,
		OwnerID:           listing.OwnerID,
		Title:             account.Title,
		Description:       account.Description,
		GameName:          account.GameName,
		ServerRegion:      account.ServerRegion,
		LoginPlatform:     account.LoginPlatform,
		RankLevel:         account.RankLevel,
		HafCoinAmount:     account.HafCoinAmount,
		AssetSummary:      assetSummary,
		ScreenshotURLS:    screenshotURLS,
		CoverURL:          publicCoverURL(listing.ID, screenshotURLS, listing.Status, listing.ReviewStatus),
		PriceCent:         listing.PriceCent,
		DepositAmountCent: listing.DepositAmountCent,
		IsAccelerated:     isAcceleratedSale(assetSummary),
		InTransaction:     listing.InTransaction,
		Status:            listing.Status,
		ReviewStatus:      reviewStatus,
		ReviewReason:      reviewReason,
		PublishedAt:       listing.PublishedAt,
		CreatedAt:         listing.CreatedAt,
		UpdatedAt:         listing.UpdatedAt,
	}
}

func normalizedReviewState(status string, reviewStatus string, reviewReason string) (string, string) {
	if status == "offline" {
		return "none", reviewReason
	}
	return reviewStatus, reviewReason
}

func marshalScreenshots(urls []string) (datatypes.JSON, error) {
	cleaned := cleanScreenshotURLs(urls)
	if len(cleaned) > 12 {
		cleaned = cleaned[:12]
	}
	raw, err := json.Marshal(cleaned)
	if err != nil {
		return nil, err
	}
	return datatypes.JSON(raw), nil
}

func marshalAssetSummary(summary map[string]any) (datatypes.JSON, error) {
	if summary == nil {
		return nil, nil
	}
	raw, err := json.Marshal(summary)
	if err != nil {
		return nil, err
	}
	return datatypes.JSON(raw), nil
}

func decodeScreenshots(raw datatypes.JSON) []string {
	if len(raw) == 0 {
		return []string{}
	}
	var urls []string
	if err := json.Unmarshal(raw, &urls); err != nil {
		return []string{}
	}
	return urls
}

func decodeAssetSummary(raw datatypes.JSON) map[string]any {
	if len(raw) == 0 {
		return nil
	}
	var summary map[string]any
	if err := json.Unmarshal(raw, &summary); err != nil {
		return nil
	}
	return summary
}

func isAcceleratedSale(summary map[string]any) bool {
	if summary == nil {
		return false
	}
	breakdown, ok := summary["price_breakdown"].(map[string]any)
	if !ok {
		return false
	}
	referenceRatio := readSummaryNumber(breakdown["seller_reference_ratio"])
	sellerRatio := readSummaryNumber(breakdown["seller_ratio"])
	acceleratedRatio := readSummaryNumber(breakdown["accelerated_sale_ratio"])
	if referenceRatio <= 0 {
		return false
	}
	return sellerRatio > referenceRatio || acceleratedRatio > referenceRatio
}

func readSummaryNumber(value any) float64 {
	switch typed := value.(type) {
	case float64:
		return typed
	case float32:
		return float64(typed)
	case int:
		return float64(typed)
	case int64:
		return float64(typed)
	case json.Number:
		number, err := typed.Float64()
		if err != nil {
			return 0
		}
		return number
	case string:
		number, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)
		if err != nil {
			return 0
		}
		return number
	default:
		return 0
	}
}

func ensurePriceBreakdown(summary map[string]any) map[string]any {
	if summary == nil {
		return map[string]any{}
	}
	breakdown, ok := summary["price_breakdown"].(map[string]any)
	if ok {
		return breakdown
	}
	breakdown = map[string]any{}
	if raw, ok := summary["price_breakdown"].(map[string]interface{}); ok {
		for key, value := range raw {
			breakdown[key] = value
		}
	}
	return breakdown
}

func calculateAdminAdjustedPrice(req AdminPriceAdjustRequest, coinWan float64, consumablePrice float64) (float64, float64, float64) {
	if req.BuyerTotalPriceCent > 0 {
		buyerTotalPrice := roundMoney(centToYuan(req.BuyerTotalPriceCent))
		buyerCoinBasePrice := roundMoney(buyerTotalPrice - consumablePrice)
		if buyerCoinBasePrice <= 0 || coinWan <= 0 {
			return 0, 0, 0
		}
		return buyerCoinBasePrice, buyerTotalPrice, roundRatio(coinWan / buyerCoinBasePrice)
	}
	if req.BuyerRatio <= 0 || coinWan <= 0 {
		return 0, 0, 0
	}
	buyerCoinBasePrice := roundMoney(coinWan / req.BuyerRatio)
	buyerTotalPrice := roundMoney(buyerCoinBasePrice + consumablePrice)
	return buyerCoinBasePrice, buyerTotalPrice, roundRatio(coinWan / buyerCoinBasePrice)
}

func roundRatio(value float64) float64 {
	if value <= 0 || math.IsNaN(value) || math.IsInf(value, 0) {
		return 0
	}
	return math.Round(value*10) / 10
}

func yuanToCent(value float64) int64 {
	return int64(math.Round(roundMoney(value) * 100))
}

func centToYuan(value int64) float64 {
	return float64(value) / 100
}

func cleanScreenshotURLs(urls []string) []string {
	cleaned := make([]string, 0, len(urls))
	seen := make(map[string]struct{}, len(urls))
	for _, url := range urls {
		url = strings.TrimSpace(url)
		if url == "" {
			continue
		}
		if _, ok := seen[url]; ok {
			continue
		}
		seen[url] = struct{}{}
		cleaned = append(cleaned, url)
	}
	return cleaned
}

func firstScreenshotURL(urls []string) string {
	if len(urls) == 0 {
		return ""
	}
	return urls[0]
}

func publicCoverURL(listingID uint64, urls []string, status string, reviewStatus string) string {
	fallback := firstScreenshotURL(urls)
	if status != "published" || reviewStatus != "approved" || extractListingObjectKey(fallback) == "" {
		return fallback
	}
	return "/api/listings/" + strconv.FormatUint(listingID, 10) + "/cover"
}

func publicScreenshotURLs(listingID uint64, urls []string, status string, reviewStatus string) []string {
	if status != "published" || reviewStatus != "approved" {
		return urls
	}
	publicURLs := make([]string, 0, len(urls))
	for index, fileURL := range urls {
		if extractListingObjectKey(fileURL) == "" {
			publicURLs = append(publicURLs, fileURL)
			continue
		}
		publicURLs = append(publicURLs, "/api/listings/"+strconv.FormatUint(listingID, 10)+"/screenshots/"+strconv.Itoa(index))
	}
	return publicURLs
}

func extractListingObjectKey(fileURL string) string {
	if fileURL == "" {
		return ""
	}
	parsed, err := url.Parse(fileURL)
	if err != nil {
		return ""
	}
	key := parsed.Query().Get("key")
	if key == "" {
		return ""
	}
	if !strings.HasPrefix(key, "listing/") || strings.Contains(key, "..") {
		return ""
	}
	return key
}

// roundMoney 使用统一的角精度（0.1元）
func roundMoney(value float64) float64 {
	return money.Round(value)
}
