package routes

import (
	"bssapp-backend/auth"
	"bssapp-backend/models"
	"bssapp-backend/queries"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"
)

// GET /api/pricing/products
func GetProductPricingListHandler(w http.ResponseWriter, r *http.Request) {
	started := time.Now()
	traceID := buildPricingTraceID(r)
	w.Header().Set("X-Trace-ID", traceID)
	claims, ok := auth.GetClaimsFromContext(r.Context())
	if !ok || claims == nil {
		log.Printf("[ProductPricing] trace=%s unauthorized method=%s path=%s", traceID, r.Method, r.URL.Path)
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}
	log.Printf("[ProductPricing] trace=%s start user=%s id=%d", traceID, claims.Username, claims.ID)

	// Cloudflare upstream timeout is lower than 180s; fail fast and return API 504 instead of CDN 524.
	ctx, cancel := context.WithTimeout(r.Context(), 110*time.Second)
	defer cancel()

	limit := 500
	if raw := strings.TrimSpace(r.URL.Query().Get("limit")); raw != "" {
		if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 && parsed <= 500 {
			limit = parsed
		}
	}
	page := 1
	if raw := strings.TrimSpace(r.URL.Query().Get("page")); raw != "" {
		if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 {
			page = parsed
		}
	}
	filters := queries.ProductPricingFilters{
		Search:       strings.TrimSpace(r.URL.Query().Get("q")),
		ProductCode:  splitCSVParam(r.URL.Query().Get("product_code")),
		BrandGroup:   splitCSVParam(r.URL.Query().Get("brand_group_selection")),
		AskiliYan:    splitCSVParam(r.URL.Query().Get("askili_yan")),
		Kategori:     splitCSVParam(r.URL.Query().Get("kategori")),
		UrunIlkGrubu: splitCSVParam(r.URL.Query().Get("urun_ilk_grubu")),
		UrunAnaGrubu: splitCSVParam(r.URL.Query().Get("urun_ana_grubu")),
		UrunAltGrubu: splitCSVParam(r.URL.Query().Get("urun_alt_grubu")),
		Icerik:       splitCSVParam(r.URL.Query().Get("icerik")),
		Karisim:      splitCSVParam(r.URL.Query().Get("karisim")),
		Marka:        splitCSVParam(r.URL.Query().Get("marka")),
	}
	if len(filters.UrunAnaGrubu) > 3 {
		http.Error(w, "Urun Ana Grubu en fazla 3 secilebilir", http.StatusBadRequest)
		return
	}
	includeTotal := true
	if raw := strings.TrimSpace(r.URL.Query().Get("include_total")); raw != "" {
		if raw == "0" || strings.EqualFold(raw, "false") {
			includeTotal = false
		}
	}
	// When primary group filters are present, COUNT(*) is acceptable and improves UX
	// (accurate totalCount/totalPages). Force includeTotal on.
	if len(filters.UrunIlkGrubu) > 0 || len(filters.UrunAnaGrubu) > 0 {
		includeTotal = true
	}

	sortBy := strings.TrimSpace(r.URL.Query().Get("sort_by"))
	desc := true
	if raw := strings.TrimSpace(r.URL.Query().Get("desc")); raw != "" {
		if raw == "0" || strings.EqualFold(raw, "false") {
			desc = false
		}
	}
	pageResult, err := queries.GetProductPricingPage(ctx, page, limit, filters, includeTotal, sortBy, desc)
	if err != nil {
		if isPricingTimeoutLike(err, ctx.Err()) {
			log.Printf(
				"[ProductPricing] trace=%s timeout user=%s id=%d duration_ms=%d err=%v",
				traceID,
				claims.Username,
				claims.ID,
				time.Since(started).Milliseconds(),
				err,
			)
			http.Error(w, "Urun fiyatlandirma listesi zaman asimina ugradi", http.StatusGatewayTimeout)
			return
		}
		log.Printf(
			"[ProductPricing] trace=%s query_error user=%s id=%d duration_ms=%d err=%v",
			traceID,
			claims.Username,
			claims.ID,
			time.Since(started).Milliseconds(),
			err,
		)
		http.Error(w, "Urun fiyatlandirma listesi alinamadi: "+err.Error(), http.StatusInternalServerError)
		return
	}
	log.Printf(
		"[ProductPricing] trace=%s success user=%s id=%d page=%d limit=%d count=%d total=%d total_pages=%d duration_ms=%d",
		traceID,
		claims.Username,
		claims.ID,
		pageResult.Page,
		limit,
		len(pageResult.Rows),
		pageResult.TotalCount,
		pageResult.TotalPages,
		time.Since(started).Milliseconds(),
	)

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Header().Set("X-Total-Count", strconv.Itoa(pageResult.TotalCount))
	w.Header().Set("X-Total-Pages", strconv.Itoa(pageResult.TotalPages))
	w.Header().Set("X-Page", strconv.Itoa(pageResult.Page))
	_ = json.NewEncoder(w).Encode(pageResult.Rows)
}

// GET /api/pricing/products/options?field=urunAnaGrubu&q=SER&limit=120
func GetProductPricingFilterOptionsHandler(w http.ResponseWriter, r *http.Request) {
	started := time.Now()
	traceID := buildPricingTraceID(r)
	w.Header().Set("X-Trace-ID", traceID)

	claims, ok := auth.GetClaimsFromContext(r.Context())
	if !ok || claims == nil {
		log.Printf("[ProductPricingOptions] trace=%s unauthorized method=%s path=%s", traceID, r.Method, r.URL.Path)
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), 12*time.Second)
	defer cancel()

	field := strings.TrimSpace(r.URL.Query().Get("field"))
	q := strings.TrimSpace(r.URL.Query().Get("q"))
	scopeUrunIlkGrubu := splitCSVParam(r.URL.Query().Get("urun_ilk_grubu"))
	limit := 120
	if raw := strings.TrimSpace(r.URL.Query().Get("limit")); raw != "" {
		if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 && parsed <= 200 {
			limit = parsed
		}
	}

	items, err := queries.GetProductPricingFilterOptions(ctx, field, q, limit, scopeUrunIlkGrubu)
	if err != nil {
		if isPricingTimeoutLike(err, ctx.Err()) {
			log.Printf(
				"[ProductPricingOptions] trace=%s timeout user=%s id=%d field=%s q=%s duration_ms=%d err=%v",
				traceID,
				claims.Username,
				claims.ID,
				field,
				q,
				time.Since(started).Milliseconds(),
				err,
			)
			http.Error(w, "Urun fiyatlandirma filtre secenekleri zaman asimina ugradi", http.StatusGatewayTimeout)
			return
		}
		log.Printf(
			"[ProductPricingOptions] trace=%s query_error user=%s id=%d field=%s q=%s duration_ms=%d err=%v",
			traceID,
			claims.Username,
			claims.ID,
			field,
			q,
			time.Since(started).Milliseconds(),
			err,
		)
		http.Error(w, "Filtre secenekleri alinamadi: "+err.Error(), http.StatusInternalServerError)
		return
	}

	type optionItem struct {
		Label string `json:"label"`
		Value string `json:"value"`
	}
	resp := struct {
		Field string       `json:"field"`
		Count int          `json:"count"`
		Items []optionItem `json:"items"`
	}{
		Field: field,
		Count: len(items),
		Items: make([]optionItem, 0, len(items)),
	}
	for _, v := range items {
		resp.Items = append(resp.Items, optionItem{Label: v, Value: v})
	}

	log.Printf(
		"[ProductPricingOptions] trace=%s success user=%s id=%d field=%s q=%s count=%d duration_ms=%d",
		traceID,
		claims.Username,
		claims.ID,
		field,
		q,
		len(items),
		time.Since(started).Milliseconds(),
	)

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	_ = json.NewEncoder(w).Encode(resp)
}

// GET /api/pricing/products/export-all
func ExportAllProductPricingHandler(w http.ResponseWriter, r *http.Request) {
	started := time.Now()
	traceID := buildPricingTraceID(r)
	w.Header().Set("X-Trace-ID", traceID)

	claims, ok := auth.GetClaimsFromContext(r.Context())
	if !ok || claims == nil {
		log.Printf("[ProductPricingExport] trace=%s unauthorized method=%s path=%s", traceID, r.Method, r.URL.Path)
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), 170*time.Second)
	defer cancel()

	filters := parseProductPricingFilters(r)
	if len(filters.UrunAnaGrubu) > 3 {
		http.Error(w, "Urun Ana Grubu en fazla 3 secilebilir", http.StatusBadRequest)
		return
	}

	sortBy := strings.TrimSpace(r.URL.Query().Get("sort_by"))
	desc := true
	if raw := strings.TrimSpace(r.URL.Query().Get("desc")); raw != "" {
		if raw == "0" || strings.EqualFold(raw, "false") {
			desc = false
		}
	}

	rows, err := queries.GetAllProductPricingRows(ctx, 1000, filters, sortBy, desc)
	if err != nil {
		if isPricingTimeoutLike(err, ctx.Err()) {
			http.Error(w, "Urun fiyatlandirma export zaman asimina ugradi", http.StatusGatewayTimeout)
			return
		}
		http.Error(w, "Urun fiyatlandirma export alinamadi: "+err.Error(), http.StatusInternalServerError)
		return
	}

	rows = filterProductPricingExportRows(rows, parseProductPricingExportFilters(r))
	priceFields := parseExportPriceFields(r.URL.Query().Get("price_fields"))
	var content string
	if len(priceFields) > 0 {
		content = buildProductPricingExportCSVWithPriceFields(rows, priceFields)
	} else {
		currencies := parseExportCurrencies(r.URL.Query().Get("currencies"))
		content = buildProductPricingExportCSV(rows, currencies)
	}
	filename := fmt.Sprintf("product_pricing_all_%s.csv", time.Now().Format("2006-01-02"))

	w.Header().Set("Content-Type", "text/csv; charset=utf-8")
	w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
	_, _ = w.Write([]byte("\uFEFF"))
	_, _ = w.Write([]byte(content))

	log.Printf(
		"[ProductPricingExport] trace=%s success user=%s id=%d rows=%d duration_ms=%d",
		traceID,
		claims.Username,
		claims.ID,
		len(rows),
		time.Since(started).Milliseconds(),
	)
}

type productPricingExportFilters struct {
	BrandGroupSelection []string
	ValueFilters        map[string][]string
	StockQtyMin         *float64
	StockQtyMax         *float64
	StockEntryFrom      string
	StockEntryTo        string
	LastPricingFrom     string
	LastPricingTo       string
}

func parseProductPricingFilters(r *http.Request) queries.ProductPricingFilters {
	return queries.ProductPricingFilters{
		Search:       strings.TrimSpace(r.URL.Query().Get("q")),
		ProductCode:  splitCSVParam(r.URL.Query().Get("product_code")),
		BrandGroup:   splitCSVParam(r.URL.Query().Get("brand_group_selection")),
		AskiliYan:    splitCSVParam(r.URL.Query().Get("askili_yan")),
		Kategori:     splitCSVParam(r.URL.Query().Get("kategori")),
		UrunIlkGrubu: splitCSVParam(r.URL.Query().Get("urun_ilk_grubu")),
		UrunAnaGrubu: splitCSVParam(r.URL.Query().Get("urun_ana_grubu")),
		UrunAltGrubu: splitCSVParam(r.URL.Query().Get("urun_alt_grubu")),
		Icerik:       splitCSVParam(r.URL.Query().Get("icerik")),
		Karisim:      splitCSVParam(r.URL.Query().Get("karisim")),
		Marka:        splitCSVParam(r.URL.Query().Get("marka")),
	}
}

func parseProductPricingExportFilters(r *http.Request) productPricingExportFilters {
	parseFloatPtr := func(raw string) *float64 {
		raw = strings.TrimSpace(raw)
		if raw == "" {
			return nil
		}
		v, err := strconv.ParseFloat(strings.ReplaceAll(raw, ",", "."), 64)
		if err != nil {
			return nil
		}
		return &v
	}

	return productPricingExportFilters{
		BrandGroupSelection: splitCSVParam(r.URL.Query().Get("brand_group_selection_local")),
		ValueFilters: map[string][]string{
			"costPrice":           splitCSVParam(r.URL.Query().Get("vf_costPrice")),
			"expenseForBasePrice": splitCSVParam(r.URL.Query().Get("vf_expenseForBasePrice")),
			"basePriceUsd":        splitCSVParam(r.URL.Query().Get("vf_basePriceUsd")),
			"basePriceTry":        splitCSVParam(r.URL.Query().Get("vf_basePriceTry")),
			"usd1":                splitCSVParam(r.URL.Query().Get("vf_usd1")),
			"usd2":                splitCSVParam(r.URL.Query().Get("vf_usd2")),
			"usd3":                splitCSVParam(r.URL.Query().Get("vf_usd3")),
			"usd4":                splitCSVParam(r.URL.Query().Get("vf_usd4")),
			"usd5":                splitCSVParam(r.URL.Query().Get("vf_usd5")),
			"usd6":                splitCSVParam(r.URL.Query().Get("vf_usd6")),
			"eur1":                splitCSVParam(r.URL.Query().Get("vf_eur1")),
			"eur2":                splitCSVParam(r.URL.Query().Get("vf_eur2")),
			"eur3":                splitCSVParam(r.URL.Query().Get("vf_eur3")),
			"eur4":                splitCSVParam(r.URL.Query().Get("vf_eur4")),
			"eur5":                splitCSVParam(r.URL.Query().Get("vf_eur5")),
			"eur6":                splitCSVParam(r.URL.Query().Get("vf_eur6")),
			"try1":                splitCSVParam(r.URL.Query().Get("vf_try1")),
			"try2":                splitCSVParam(r.URL.Query().Get("vf_try2")),
			"try3":                splitCSVParam(r.URL.Query().Get("vf_try3")),
			"try4":                splitCSVParam(r.URL.Query().Get("vf_try4")),
			"try5":                splitCSVParam(r.URL.Query().Get("vf_try5")),
			"try6":                splitCSVParam(r.URL.Query().Get("vf_try6")),
		},
		StockQtyMin:     parseFloatPtr(r.URL.Query().Get("stock_qty_min")),
		StockQtyMax:     parseFloatPtr(r.URL.Query().Get("stock_qty_max")),
		StockEntryFrom:  strings.TrimSpace(r.URL.Query().Get("stock_entry_from")),
		StockEntryTo:    strings.TrimSpace(r.URL.Query().Get("stock_entry_to")),
		LastPricingFrom: strings.TrimSpace(r.URL.Query().Get("last_pricing_from")),
		LastPricingTo:   strings.TrimSpace(r.URL.Query().Get("last_pricing_to")),
	}
}

func filterProductPricingExportRows(rows []models.ProductPricing, f productPricingExportFilters) []models.ProductPricing {
	if len(rows) == 0 {
		return rows
	}

	out := make([]models.ProductPricing, 0, len(rows))
	for _, row := range rows {
		if len(f.BrandGroupSelection) > 0 && !containsText(f.BrandGroupSelection, row.BrandGroupSec) {
			continue
		}
		if !matchesProductPricingValueFilters(row, f.ValueFilters) {
			continue
		}
		if f.StockQtyMin != nil && row.StockQty < *f.StockQtyMin {
			continue
		}
		if f.StockQtyMax != nil && row.StockQty > *f.StockQtyMax {
			continue
		}
		if !matchesDateRangeString(row.StockEntryDate, f.StockEntryFrom, f.StockEntryTo) {
			continue
		}
		if !matchesDateRangeString(row.LastPricingDate, f.LastPricingFrom, f.LastPricingTo) {
			continue
		}
		out = append(out, row)
	}
	return out
}

func parseExportCurrencies(raw string) []string {
	requested := splitCSVParam(raw)
	if len(requested) == 0 {
		return []string{"USD", "EUR", "TRY"}
	}
	out := make([]string, 0, 3)
	seen := map[string]bool{}
	for _, item := range requested {
		cur := strings.ToUpper(strings.TrimSpace(item))
		if cur != "USD" && cur != "EUR" && cur != "TRY" {
			continue
		}
		if seen[cur] {
			continue
		}
		seen[cur] = true
		out = append(out, cur)
	}
	if len(out) == 0 {
		return []string{"USD", "EUR", "TRY"}
	}
	return out
}

func parseExportPriceFields(raw string) []string {
	requested := splitCSVParam(raw)
	if len(requested) == 0 {
		return nil
	}
	out := make([]string, 0, 18)
	seen := map[string]bool{}
	for _, item := range requested {
		v := strings.ToLower(strings.TrimSpace(item))
		if v == "" {
			continue
		}
		// Accept both "usd1" and "USD1" etc.
		switch v {
		case "usd1", "usd2", "usd3", "usd4", "usd5", "usd6",
			"eur1", "eur2", "eur3", "eur4", "eur5", "eur6",
			"try1", "try2", "try3", "try4", "try5", "try6":
			// ok
		default:
			continue
		}
		if seen[v] {
			continue
		}
		seen[v] = true
		out = append(out, v)
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func matchesProductPricingValueFilters(row models.ProductPricing, filters map[string][]string) bool {
	if len(filters) == 0 {
		return true
	}
	for field, selected := range filters {
		if len(selected) == 0 {
			continue
		}
		if !containsText(selected, productPricingValueKey(row, field)) {
			return false
		}
	}
	return true
}

func productPricingValueKey(row models.ProductPricing, field string) string {
	value := 0.0
	switch field {
	case "costPrice":
		value = row.CostPrice
	case "expenseForBasePrice":
		value = 0
	case "basePriceUsd":
		value = row.BasePriceUsd
	case "basePriceTry":
		value = row.BasePriceTry
	case "usd1":
		value = row.USD1
	case "usd2":
		value = row.USD2
	case "usd3":
		value = row.USD3
	case "usd4":
		value = row.USD4
	case "usd5":
		value = row.USD5
	case "usd6":
		value = row.USD6
	case "eur1":
		value = row.EUR1
	case "eur2":
		value = row.EUR2
	case "eur3":
		value = row.EUR3
	case "eur4":
		value = row.EUR4
	case "eur5":
		value = row.EUR5
	case "eur6":
		value = row.EUR6
	case "try1":
		value = row.TRY1
	case "try2":
		value = row.TRY2
	case "try3":
		value = row.TRY3
	case "try4":
		value = row.TRY4
	case "try5":
		value = row.TRY5
	case "try6":
		value = row.TRY6
	}
	return fmt.Sprintf("%.2f", value)
}

func matchesDateRangeString(value string, from string, to string) bool {
	value = strings.TrimSpace(value)
	from = strings.TrimSpace(from)
	to = strings.TrimSpace(to)
	if from == "" && to == "" {
		return true
	}
	if value == "" {
		return false
	}
	if from != "" && value < from {
		return false
	}
	if to != "" && value > to {
		return false
	}
	return true
}

func containsText(list []string, value string) bool {
	value = strings.TrimSpace(value)
	for _, item := range list {
		if strings.TrimSpace(item) == value {
			return true
		}
	}
	return false
}

func csvEscape(value string) string {
	text := strings.ReplaceAll(strings.ReplaceAll(strings.TrimSpace(value), "\r", " "), "\n", " ")
	if strings.Contains(text, ";") || strings.Contains(text, "\"") {
		text = `"` + strings.ReplaceAll(text, `"`, `""`) + `"`
	}
	return text
}

func csvFloat(value float64) string {
	return strings.ReplaceAll(fmt.Sprintf("%.2f", value), ".", ",")
}

func exportPriceFieldTitle(field string) string {
	field = strings.ToLower(strings.TrimSpace(field))
	if len(field) != 4 {
		return strings.ToUpper(field)
	}
	cur := strings.ToUpper(field[:3])
	lv := field[3:]
	return fmt.Sprintf("%s %s", cur, lv)
}

func exportPriceFieldValue(row models.ProductPricing, field string) float64 {
	switch strings.ToLower(strings.TrimSpace(field)) {
	case "usd1":
		return row.USD1
	case "usd2":
		return row.USD2
	case "usd3":
		return row.USD3
	case "usd4":
		return row.USD4
	case "usd5":
		return row.USD5
	case "usd6":
		return row.USD6
	case "eur1":
		return row.EUR1
	case "eur2":
		return row.EUR2
	case "eur3":
		return row.EUR3
	case "eur4":
		return row.EUR4
	case "eur5":
		return row.EUR5
	case "eur6":
		return row.EUR6
	case "try1":
		return row.TRY1
	case "try2":
		return row.TRY2
	case "try3":
		return row.TRY3
	case "try4":
		return row.TRY4
	case "try5":
		return row.TRY5
	case "try6":
		return row.TRY6
	default:
		return 0
	}
}

func buildProductPricingExportCSVWithPriceFields(rows []models.ProductPricing, priceFields []string) string {
	var b strings.Builder

	headers := []string{
		"MARKA GRUBU SECIMI",
		"MARKA",
		"URUN KODU",
		"STOK ADET",
		"STOK GIRIS TARIHI",
		"SON MALIYETLENDIRME",
		"SON FIYATLANDIRMA TARIHI",
		"ASKILI YAN",
		"KATEGORI",
		"URUN ILK GRUBU",
		"URUN ANA GRUBU",
		"URUN ALT GRUBU",
		"ICERIK",
		"KARISIM",
		"MALIYET FIYATI",
		"TABAN FIYAT MASRAF",
		"TABAN USD",
		"TABAN TRY",
	}
	for _, h := range headers {
		b.WriteString(csvEscape(h))
		b.WriteString(";")
	}
	for i, pf := range priceFields {
		b.WriteString(csvEscape(exportPriceFieldTitle(pf)))
		if i == len(priceFields)-1 {
			b.WriteString("\n")
		} else {
			b.WriteString(";")
		}
	}

	for _, row := range rows {
		base := []string{
			row.BrandGroupSec,
			row.Marka,
			row.ProductCode,
			csvFloat(row.StockQty),
			row.StockEntryDate,
			row.LastCostingDate,
			row.LastPricingDate,
			row.AskiliYan,
			row.Kategori,
			row.UrunIlkGrubu,
			row.UrunAnaGrubu,
			row.UrunAltGrubu,
			row.Icerik,
			row.Karisim,
			csvFloat(row.CostPrice),
			csvFloat(0),
			csvFloat(row.BasePriceUsd),
			csvFloat(row.BasePriceTry),
		}
		for _, item := range base {
			b.WriteString(csvEscape(item))
			b.WriteString(";")
		}
		for i, pf := range priceFields {
			b.WriteString(csvEscape(csvFloat(exportPriceFieldValue(row, pf))))
			if i == len(priceFields)-1 {
				b.WriteString("\n")
			} else {
				b.WriteString(";")
			}
		}
	}

	return b.String()
}

func buildProductPricingExportCSV(rows []models.ProductPricing, currencies []string) string {
	// Backward compatible export: USD/EUR/TRY and all 1..6 tiers per currency.
	fields := make([]string, 0, 18)
	for _, cur := range currencies {
		cur = strings.ToUpper(strings.TrimSpace(cur))
		switch cur {
		case "USD", "EUR", "TRY":
		default:
			continue
		}
		for lv := 1; lv <= 6; lv++ {
			fields = append(fields, strings.ToLower(fmt.Sprintf("%s%d", cur, lv)))
		}
	}
	if len(fields) == 0 {
		fields = []string{"usd1", "usd2", "usd3", "usd4", "usd5", "usd6", "eur1", "eur2", "eur3", "eur4", "eur5", "eur6", "try1", "try2", "try3", "try4", "try5", "try6"}
	}
	return buildProductPricingExportCSVWithPriceFields(rows, fields)
}

func productPricingCurrencyValues(row models.ProductPricing, currency string) []float64 {
	switch currency {
	case "USD":
		return []float64{row.USD1, row.USD2, row.USD3, row.USD4, row.USD5, row.USD6}
	case "EUR":
		return []float64{row.EUR1, row.EUR2, row.EUR3, row.EUR4, row.EUR5, row.EUR6}
	default:
		return []float64{row.TRY1, row.TRY2, row.TRY3, row.TRY4, row.TRY5, row.TRY6}
	}
}

func buildPricingTraceID(r *http.Request) string {
	if r != nil {
		if id := strings.TrimSpace(r.Header.Get("X-Request-ID")); id != "" {
			return id
		}
		if id := strings.TrimSpace(r.Header.Get("X-Correlation-ID")); id != "" {
			return id
		}
	}
	return "pricing-" + strconv.FormatInt(time.Now().UnixNano(), 36)
}

func isPricingTimeoutLike(err error, ctxErr error) bool {
	if errors.Is(err, context.DeadlineExceeded) || errors.Is(ctxErr, context.DeadlineExceeded) {
		return true
	}
	if err == nil {
		return false
	}
	e := strings.ToLower(err.Error())
	return strings.Contains(e, "timeout") ||
		strings.Contains(e, "i/o timeout") ||
		strings.Contains(e, "wsarecv") ||
		strings.Contains(e, "connection attempt failed") ||
		strings.Contains(e, "no connection could be made") ||
		strings.Contains(e, "failed to respond")
}

func splitCSVParam(raw string) []string {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil
	}
	parts := strings.Split(raw, ",")
	out := make([]string, 0, len(parts))
	for _, p := range parts {
		p = strings.TrimSpace(p)
		if p == "" {
			continue
		}
		out = append(out, p)
	}
	return out
}
