package queries

import (
	"bssapp-backend/db"
	"bssapp-backend/models"
	"context"
	"crypto/md5"
	"database/sql"
	"encoding/hex"
	"fmt"
	"math"
	"sort"
	"strings"
	"time"
)

type PricingFxRateCacheRow struct {
	RateDate string  `json:"rate_date"`
	UsdTry   float64 `json:"usd_try"`
	EurTry   float64 `json:"eur_try"`
	UsdEur   float64 `json:"usd_eur"`
}

type ProductPricingSnapshotCalcRequest struct {
	ProductCodes   []string
	Filters        ProductPricingFilters
	RateDate       string
	ForceFxRefresh bool
}

type ProductPricingSnapshotCalcResult struct {
	RateDate   string  `json:"rate_date"`
	UsdTry     float64 `json:"usd_try"`
	EurTry     float64 `json:"eur_try"`
	UsdEur     float64 `json:"usd_eur"`
	Requested  int     `json:"requested"`
	Calculated int     `json:"calculated"`
	Skipped    int     `json:"skipped"`
}

type ProductPricingSnapshotPreviewRow struct {
	ProductCode  string  `json:"product_code"`
	AnchorMode   string  `json:"anchor_mode"`
	BasePriceUsd float64 `json:"base_price_usd"`
	BasePriceTry float64 `json:"base_price_try"`
	USD1         float64 `json:"usd1"`
	USD2         float64 `json:"usd2"`
	USD3         float64 `json:"usd3"`
	USD4         float64 `json:"usd4"`
	USD5         float64 `json:"usd5"`
	USD6         float64 `json:"usd6"`
	EUR1         float64 `json:"eur1"`
	EUR2         float64 `json:"eur2"`
	EUR3         float64 `json:"eur3"`
	EUR4         float64 `json:"eur4"`
	EUR5         float64 `json:"eur5"`
	EUR6         float64 `json:"eur6"`
	TRY1         float64 `json:"try1"`
	TRY2         float64 `json:"try2"`
	TRY3         float64 `json:"try3"`
	TRY4         float64 `json:"try4"`
	TRY5         float64 `json:"try5"`
	TRY6         float64 `json:"try6"`
}

type ProductPricingSnapshotPreviewResult struct {
	RateDate   string                             `json:"rate_date"`
	UsdTry     float64                            `json:"usd_try"`
	EurTry     float64                            `json:"eur_try"`
	UsdEur     float64                            `json:"usd_eur"`
	Requested  int                                `json:"requested"`
	Calculated int                                `json:"calculated"`
	Skipped    int                                `json:"skipped"`
	Rows       []ProductPricingSnapshotPreviewRow `json:"rows"`
}

func resolvePricingFxRateByDate(ctx context.Context, pg *sql.DB, rateDate string, forceRefresh bool, persist bool) (PricingFxRateCacheRow, error) {
	var out PricingFxRateCacheRow
	rateDate = normalizeCalcDate(rateDate)
	if rateDate == "" {
		rateDate = time.Now().Format("2006-01-02")
	}

	if !forceRefresh {
		err := pg.QueryRowContext(ctx, `
SELECT
  TO_CHAR(rate_date, 'YYYY-MM-DD'),
  usd_try::float8,
  eur_try::float8,
  usd_eur::float8
FROM mk_fx_rate_cache
WHERE rate_date=$1::date
`, rateDate).Scan(&out.RateDate, &out.UsdTry, &out.EurTry, &out.UsdEur)
		if err == nil {
			return out, nil
		}
		if err != nil && err != sql.ErrNoRows {
			return out, err
		}
	}

	if db.MssqlDB == nil {
		return out, fmt.Errorf("mssql pricing db not available")
	}
	row, err := GetProductionHasCostDetailExchangeRatesByDate(ctx, db.MssqlDB, rateDate)
	if err != nil {
		return out, err
	}
	var (
		rateDateResolved string
		usdTry           float64
		eurTry           float64
		gbpIgnored       float64
	)
	if err := row.Scan(&rateDateResolved, &usdTry, &eurTry, &gbpIgnored); err != nil {
		return out, err
	}
	rateDateResolved = normalizeCalcDate(rateDateResolved)
	if rateDateResolved == "" {
		rateDateResolved = rateDate
	}
	usdEur := 0.0
	if usdTry > 0 && eurTry > 0 {
		usdEur = roundCalcValue(usdTry / eurTry)
	}

	if persist {
		if _, err := pg.ExecContext(ctx, `
INSERT INTO mk_fx_rate_cache (
  rate_date, usd_try, eur_try, usd_eur, source_system, source_updated_at, created_at, updated_at
)
VALUES ($1::date, $2, $3, $4, 'MSSQL', now(), now(), now())
ON CONFLICT (rate_date)
DO UPDATE SET
  usd_try=EXCLUDED.usd_try,
  eur_try=EXCLUDED.eur_try,
  usd_eur=EXCLUDED.usd_eur,
  source_system=EXCLUDED.source_system,
  source_updated_at=EXCLUDED.source_updated_at,
  updated_at=now()
`, rateDateResolved, usdTry, eurTry, usdEur); err != nil {
			return out, err
		}
	}

	out = PricingFxRateCacheRow{
		RateDate: rateDateResolved,
		UsdTry:   usdTry,
		EurTry:   eurTry,
		UsdEur:   usdEur,
	}
	return out, nil
}

func SyncPricingFxRateCacheByDate(ctx context.Context, pg *sql.DB, rateDate string, forceRefresh bool) (PricingFxRateCacheRow, error) {
	return resolvePricingFxRateByDate(ctx, pg, rateDate, forceRefresh, true)
}

func CalculateProductPricingSnapshots(ctx context.Context, pg *sql.DB, req ProductPricingSnapshotCalcRequest) (ProductPricingSnapshotCalcResult, error) {
	var result ProductPricingSnapshotCalcResult
	rateRow, err := resolvePricingFxRateByDate(ctx, pg, req.RateDate, req.ForceFxRefresh, true)
	if err != nil {
		return result, err
	}
	result.RateDate = rateRow.RateDate
	result.UsdTry = rateRow.UsdTry
	result.EurTry = rateRow.EurTry
	result.UsdEur = rateRow.UsdEur
	if rateRow.UsdTry <= 0 || rateRow.EurTry <= 0 || rateRow.UsdEur <= 0 {
		return result, fmt.Errorf("invalid fx rates for date %s", rateRow.RateDate)
	}

	filters := req.Filters
	if len(req.ProductCodes) > 0 {
		filters.ProductCode = dedupeTrimmedStrings(req.ProductCodes)
	}

	rows, err := GetAllProductPricingRows(ctx, 1000, filters, "productCode", false)
	if err != nil {
		return result, err
	}
	result.Requested = len(rows)
	if len(rows) == 0 {
		return result, nil
	}

	ruleRows, err := ListPricingParameterRules(ctx, pg, PricingRuleOptionFilters{})
	if err != nil {
		return result, err
	}
	rulesByScope := make(map[string]PricingParameterRuleRow, len(ruleRows))
	for _, item := range ruleRows {
		rulesByScope[item.ScopeKey] = item
	}

	tx, err := pg.BeginTx(ctx, nil)
	if err != nil {
		return result, err
	}
	defer tx.Rollback()

	for _, product := range rows {
		scopeKey := pricingParameterScopeKey(pricingParameterRow{
			AskiliYan:     strings.TrimSpace(product.AskiliYan),
			Kategori:      strings.TrimSpace(product.Kategori),
			UrunIlkGrubu:  strings.TrimSpace(product.UrunIlkGrubu),
			UrunAnaGrubu:  strings.TrimSpace(product.UrunAnaGrubu),
			UrunAltGrubu:  strings.TrimSpace(product.UrunAltGrubu),
			Icerik:        strings.TrimSpace(product.Icerik),
			Marka:         strings.TrimSpace(product.Marka),
			BrandCode:     strings.TrimSpace(product.BrandCode),
			BrandGroupSec: strings.TrimSpace(product.BrandGroupSec),
		})
		ruleItem, ok := rulesByScope[scopeKey]
		if !ok || ruleItem.Rule == nil {
			result.Skipped++
			continue
		}
		if !ruleItem.Rule.CalcEnabled || !ruleItem.Rule.IsActive {
			result.Skipped++
			continue
		}

		snapshot, ok := buildPricingSnapshotRow(product, ruleItem, rateRow)
		if !ok {
			result.Skipped++
			continue
		}
		if err := upsertPricingSnapshot(ctx, tx, snapshot); err != nil {
			return result, err
		}
		result.Calculated++
	}

	if err := tx.Commit(); err != nil {
		return result, err
	}
	return result, nil
}

func PreviewProductPricingSnapshots(ctx context.Context, pg *sql.DB, req ProductPricingSnapshotCalcRequest) (ProductPricingSnapshotPreviewResult, error) {
	var result ProductPricingSnapshotPreviewResult
	rateRow, err := resolvePricingFxRateByDate(ctx, pg, req.RateDate, req.ForceFxRefresh, false)
	if err != nil {
		return result, err
	}
	result.RateDate = rateRow.RateDate
	result.UsdTry = rateRow.UsdTry
	result.EurTry = rateRow.EurTry
	result.UsdEur = rateRow.UsdEur
	if rateRow.UsdTry <= 0 || rateRow.EurTry <= 0 || rateRow.UsdEur <= 0 {
		return result, fmt.Errorf("invalid fx rates for date %s", rateRow.RateDate)
	}

	filters := req.Filters
	if len(req.ProductCodes) > 0 {
		filters.ProductCode = dedupeTrimmedStrings(req.ProductCodes)
	}

	rows, err := GetAllProductPricingRows(ctx, 1000, filters, "productCode", false)
	if err != nil {
		return result, err
	}
	result.Requested = len(rows)
	if len(rows) == 0 {
		result.Rows = []ProductPricingSnapshotPreviewRow{}
		return result, nil
	}

	ruleRows, err := ListPricingParameterRules(ctx, pg, PricingRuleOptionFilters{})
	if err != nil {
		return result, err
	}
	rulesByScope := make(map[string]PricingParameterRuleRow, len(ruleRows))
	for _, item := range ruleRows {
		rulesByScope[item.ScopeKey] = item
	}

	outRows := make([]ProductPricingSnapshotPreviewRow, 0, len(rows))
	for _, product := range rows {
		scopeKey := pricingParameterScopeKey(pricingParameterRow{
			AskiliYan:     strings.TrimSpace(product.AskiliYan),
			Kategori:      strings.TrimSpace(product.Kategori),
			UrunIlkGrubu:  strings.TrimSpace(product.UrunIlkGrubu),
			UrunAnaGrubu:  strings.TrimSpace(product.UrunAnaGrubu),
			UrunAltGrubu:  strings.TrimSpace(product.UrunAltGrubu),
			Icerik:        strings.TrimSpace(product.Icerik),
			Marka:         strings.TrimSpace(product.Marka),
			BrandCode:     strings.TrimSpace(product.BrandCode),
			BrandGroupSec: strings.TrimSpace(product.BrandGroupSec),
		})
		ruleItem, ok := rulesByScope[scopeKey]
		if !ok || ruleItem.Rule == nil {
			result.Skipped++
			continue
		}
		if !ruleItem.Rule.CalcEnabled || !ruleItem.Rule.IsActive {
			result.Skipped++
			continue
		}
		snapshot, ok := buildPricingSnapshotRow(product, ruleItem, rateRow)
		if !ok {
			result.Skipped++
			continue
		}
		outRows = append(outRows, previewRowFromSnapshot(snapshot))
		result.Calculated++
	}
	result.Rows = outRows
	return result, nil
}

type pricingSnapshotRow struct {
	ProductCode        string
	PricingParameterID int64
	RuleID             string
	StrategyCode       string
	AnchorMode         string
	FxDate             string
	CostDate           string
	BasePriceTry       float64
	BasePriceUsd       float64
	Try                [6]float64
	Usd                [6]float64
	Eur                [6]float64
	CalcHash           string
}

func buildPricingSnapshotRow(product models.ProductPricing, ruleItem PricingParameterRuleRow, fx PricingFxRateCacheRow) (pricingSnapshotRow, bool) {
	var out pricingSnapshotRow
	rule := ruleItem.Rule
	if rule == nil {
		return out, false
	}

	anchorMode := strings.ToUpper(strings.TrimSpace(rule.AnchorMode))
	if anchorMode != "TRY" && anchorMode != "USD" {
		anchorMode = "USD"
	}
	strategyCode := strings.ToUpper(strings.TrimSpace(rule.StrategyCode))
	if strategyCode != "CORE" && strategyCode != "PREMIUM" && strategyCode != "SARTORIAL" {
		strategyCode = strings.ToUpper(strings.TrimSpace(product.BrandGroupSec))
	}
	if strategyCode != "CORE" && strategyCode != "PREMIUM" && strategyCode != "SARTORIAL" {
		strategyCode = "CORE"
	}

	costUSD := roundCalcValue(product.CostPrice)
	if costUSD <= 0 {
		return out, false
	}

	baseUSD := 0.0
	baseTRY := 0.0
	switch anchorMode {
	case "TRY":
		if rule.TryBase > 0 {
			baseTRY = roundCalcValue(costUSD * fx.UsdTry * rule.TryBase)
		} else if product.BasePriceTry > 0 {
			baseTRY = roundCalcValue(product.BasePriceTry)
		} else if product.BasePriceUsd > 0 {
			baseTRY = roundCalcValue(product.BasePriceUsd * fx.UsdTry)
		} else if rule.UsdBase > 0 {
			baseTRY = roundCalcValue(costUSD * rule.UsdBase * fx.UsdTry)
		}
		if baseTRY <= 0 {
			return out, false
		}
		baseUSD = roundCalcValue(baseTRY / fx.UsdTry)
	default:
		if rule.UsdBase > 0 {
			baseUSD = roundCalcValue(costUSD * rule.UsdBase)
		} else if product.BasePriceUsd > 0 {
			baseUSD = roundCalcValue(product.BasePriceUsd)
		} else if product.BasePriceTry > 0 {
			baseUSD = roundCalcValue(product.BasePriceTry / fx.UsdTry)
		}
		if baseUSD <= 0 {
			return out, false
		}
		baseTRY = roundCalcValue(baseUSD * fx.UsdTry)
	}
	baseEUR := roundCalcValue(baseUSD * fx.UsdEur)

	tryBaseForCalc := baseTRY
	usdBaseForCalc := baseUSD
	eurBaseForCalc := baseEUR
	if tryBaseForCalc <= 0 || usdBaseForCalc <= 0 || eurBaseForCalc <= 0 {
		return out, false
	}

	tryMultipliers := [6]float64{rule.Try1, rule.Try2, rule.Try3, rule.Try4, rule.Try5, rule.Try6}
	usdMultipliers := [6]float64{rule.Usd1, rule.Usd2, rule.Usd3, rule.Usd4, rule.Usd5, rule.Usd6}
	eurMultipliers := [6]float64{rule.Eur1, rule.Eur2, rule.Eur3, rule.Eur4, rule.Eur5, rule.Eur6}

	prevTry := tryBaseForCalc
	prevUsd := usdBaseForCalc
	prevEur := eurBaseForCalc
	for i := 0; i < 6; i++ {
		tryRaw := prevTry * tryMultipliers[i]
		usdRaw := prevUsd * usdMultipliers[i]
		eurRaw := prevEur * eurMultipliers[i]

		tryStep := rule.TryWholesaleStep
		usdStep := rule.UsdWholesaleStep
		eurStep := rule.EurWholesaleStep
		if i == 5 {
			out.Try[i] = applyRetailRounding(tryRaw, rule.TryWholesaleStep, rule.TryRetailStep, rule.TryRetailMode)
			out.Usd[i] = applyRetailRounding(usdRaw, rule.UsdWholesaleStep, rule.UsdRetailStep, rule.UsdRetailMode)
			out.Eur[i] = applyRetailRounding(eurRaw, rule.EurWholesaleStep, rule.EurRetailStep, rule.EurRetailMode)
			prevTry = out.Try[i]
			prevUsd = out.Usd[i]
			prevEur = out.Eur[i]
			continue
		}

		out.Try[i] = roundUpStep(tryRaw, tryStep)
		out.Usd[i] = roundUpStep(usdRaw, usdStep)
		out.Eur[i] = roundUpStep(eurRaw, eurStep)
		prevTry = out.Try[i]
		prevUsd = out.Usd[i]
		prevEur = out.Eur[i]
	}

	out.ProductCode = strings.TrimSpace(product.ProductCode)
	out.PricingParameterID = ruleItem.PricingParameterID
	out.RuleID = strings.TrimSpace(rule.ID)
	out.StrategyCode = strategyCode
	out.AnchorMode = anchorMode
	out.FxDate = fx.RateDate
	out.CostDate = normalizeCalcDate(product.LastCostingDate)
	out.BasePriceTry = baseTRY
	out.BasePriceUsd = baseUSD
	out.CalcHash = pricingSnapshotHash(out, fx)
	return out, true
}

func applyRetailRounding(raw, wholesaleStep, retailStep float64, retailMode string) float64 {
	baseRounded := roundUpStep(raw, wholesaleStep)
	mode := normalizeRetailMode(retailMode)
	switch mode {
	case "END_99":
		return roundUpToEnding(baseRounded, 99)
	case "END_49":
		return roundUpToEnding(baseRounded, 49)
	case "BAND_99":
		return roundUpToBandEnding(baseRounded, retailStep, 99)
	case "BAND_49":
		return roundUpToBandEnding(baseRounded, retailStep, 49)
	default:
		if retailStep > 0 {
			return roundUpStep(baseRounded, retailStep)
		}
		return baseRounded
	}
}

func roundUpToEnding(value float64, ending int) float64 {
	value = roundCalcValue(value)
	if value <= 0 {
		return 0
	}
	switch ending {
	case 99:
		return roundCalcValue(psychologicalEnding99(value))
	case 49:
		return roundCalcValue(psychologicalEnding49(value))
	default:
		whole := math.Floor(value + 1e-9)
		candidate := whole + (float64(ending) / 100.0)
		if candidate+1e-9 < value {
			candidate = whole + 1 + (float64(ending) / 100.0)
		}
		return roundCalcValue(candidate)
	}
}

func roundUpToBandEnding(value, band float64, ending int) float64 {
	value = roundCalcValue(value)
	band = roundCalcValue(band)
	if value <= 0 {
		return 0
	}
	if band <= 0 {
		return roundUpToEnding(value, ending)
	}
	units := math.Ceil((value - 1e-9) / band)
	candidate := (units * band) - 1 + (float64(ending) / 100.0)
	if candidate+1e-9 < value {
		candidate = ((units + 1) * band) - 1 + (float64(ending) / 100.0)
	}
	return roundCalcValue(candidate)
}

func psychologicalEnding99(value float64) float64 {
	whole := math.Floor(value + 1e-9)
	fraction := value - whole
	if fraction >= 0.90 {
		return whole + 0.99
	}
	return whole - 0.01
}

func psychologicalEnding49(value float64) float64 {
	whole := math.Floor(value + 1e-9)
	fraction := value - whole
	if fraction >= 0.40 {
		return whole + 0.49
	}
	return whole - 0.51
}

func upsertPricingSnapshot(ctx context.Context, tx *sql.Tx, row pricingSnapshotRow) error {
	_, err := tx.ExecContext(ctx, `
INSERT INTO mk_price_snapshot (
  product_code, pricing_parameter_id, rule_id, strategy_code, anchor_mode, fx_date, cost_date,
  base_price_try, base_price_usd,
  try1, try2, try3, try4, try5, try6,
  usd1, usd2, usd3, usd4, usd5, usd6,
  eur1, eur2, eur3, eur4, eur5, eur6,
  calc_hash, created_at, updated_at
)
VALUES (
  $1,$2,NULLIF($3,'')::uuid,$4,$5,$6::date,NULLIF($7,'')::date,
  $8,$9,
  $10,$11,$12,$13,$14,$15,
  $16,$17,$18,$19,$20,$21,
  $22,$23,$24,$25,$26,$27,
  $28,now(),now()
)
ON CONFLICT (product_code, pricing_parameter_id)
DO UPDATE SET
  rule_id=NULLIF(EXCLUDED.rule_id::text,'')::uuid,
  strategy_code=EXCLUDED.strategy_code,
  anchor_mode=EXCLUDED.anchor_mode,
  fx_date=EXCLUDED.fx_date,
  cost_date=EXCLUDED.cost_date,
  base_price_try=EXCLUDED.base_price_try,
  base_price_usd=EXCLUDED.base_price_usd,
  try1=EXCLUDED.try1,
  try2=EXCLUDED.try2,
  try3=EXCLUDED.try3,
  try4=EXCLUDED.try4,
  try5=EXCLUDED.try5,
  try6=EXCLUDED.try6,
  usd1=EXCLUDED.usd1,
  usd2=EXCLUDED.usd2,
  usd3=EXCLUDED.usd3,
  usd4=EXCLUDED.usd4,
  usd5=EXCLUDED.usd5,
  usd6=EXCLUDED.usd6,
  eur1=EXCLUDED.eur1,
  eur2=EXCLUDED.eur2,
  eur3=EXCLUDED.eur3,
  eur4=EXCLUDED.eur4,
  eur5=EXCLUDED.eur5,
  eur6=EXCLUDED.eur6,
  calc_hash=EXCLUDED.calc_hash,
  updated_at=now()
`, row.ProductCode, row.PricingParameterID, row.RuleID, row.StrategyCode, row.AnchorMode, row.FxDate, row.CostDate,
		row.BasePriceTry, row.BasePriceUsd,
		row.Try[0], row.Try[1], row.Try[2], row.Try[3], row.Try[4], row.Try[5],
		row.Usd[0], row.Usd[1], row.Usd[2], row.Usd[3], row.Usd[4], row.Usd[5],
		row.Eur[0], row.Eur[1], row.Eur[2], row.Eur[3], row.Eur[4], row.Eur[5],
		row.CalcHash,
	)
	return err
}

func previewRowFromSnapshot(row pricingSnapshotRow) ProductPricingSnapshotPreviewRow {
	return ProductPricingSnapshotPreviewRow{
		ProductCode:  row.ProductCode,
		AnchorMode:   row.AnchorMode,
		BasePriceUsd: roundCalcValue(row.BasePriceUsd),
		BasePriceTry: roundCalcValue(row.BasePriceTry),
		USD1:         roundCalcValue(row.Usd[0]),
		USD2:         roundCalcValue(row.Usd[1]),
		USD3:         roundCalcValue(row.Usd[2]),
		USD4:         roundCalcValue(row.Usd[3]),
		USD5:         roundCalcValue(row.Usd[4]),
		USD6:         roundCalcValue(row.Usd[5]),
		EUR1:         roundCalcValue(row.Eur[0]),
		EUR2:         roundCalcValue(row.Eur[1]),
		EUR3:         roundCalcValue(row.Eur[2]),
		EUR4:         roundCalcValue(row.Eur[3]),
		EUR5:         roundCalcValue(row.Eur[4]),
		EUR6:         roundCalcValue(row.Eur[5]),
		TRY1:         roundCalcValue(row.Try[0]),
		TRY2:         roundCalcValue(row.Try[1]),
		TRY3:         roundCalcValue(row.Try[2]),
		TRY4:         roundCalcValue(row.Try[3]),
		TRY5:         roundCalcValue(row.Try[4]),
		TRY6:         roundCalcValue(row.Try[5]),
	}
}

func roundUpStep(value, step float64) float64 {
	value = roundCalcValue(value)
	if value <= 0 {
		return 0
	}
	step = roundCalcValue(step)
	if step <= 0 {
		return value
	}
	units := math.Ceil((value - 1e-9) / step)
	return roundCalcValue(units * step)
}

func roundCalcValue(value float64) float64 {
	if !isFiniteCalc(value) {
		return 0
	}
	return math.Round(value*1_000_000) / 1_000_000
}

func isFiniteCalc(value float64) bool {
	return !math.IsNaN(value) && !math.IsInf(value, 0)
}

func normalizeCalcDate(value string) string {
	value = strings.TrimSpace(value)
	if value == "" {
		return ""
	}
	if len(value) >= 10 {
		value = value[:10]
	}
	if _, err := time.Parse("2006-01-02", value); err != nil {
		return ""
	}
	return value
}

func dedupeTrimmedStrings(values []string) []string {
	seen := map[string]struct{}{}
	out := make([]string, 0, len(values))
	for _, raw := range values {
		val := strings.TrimSpace(raw)
		if val == "" {
			continue
		}
		if _, ok := seen[val]; ok {
			continue
		}
		seen[val] = struct{}{}
		out = append(out, val)
	}
	sort.Strings(out)
	return out
}

func pricingSnapshotHash(row pricingSnapshotRow, fx PricingFxRateCacheRow) string {
	parts := []string{
		row.ProductCode,
		fmt.Sprintf("%d", row.PricingParameterID),
		row.RuleID,
		row.StrategyCode,
		row.AnchorMode,
		row.FxDate,
		row.CostDate,
		fmt.Sprintf("%.6f", row.BasePriceTry),
		fmt.Sprintf("%.6f", row.BasePriceUsd),
		fmt.Sprintf("%.6f", fx.UsdTry),
		fmt.Sprintf("%.6f", fx.EurTry),
		fmt.Sprintf("%.6f", fx.UsdEur),
	}
	for _, value := range row.Try {
		parts = append(parts, fmt.Sprintf("%.6f", value))
	}
	for _, value := range row.Usd {
		parts = append(parts, fmt.Sprintf("%.6f", value))
	}
	for _, value := range row.Eur {
		parts = append(parts, fmt.Sprintf("%.6f", value))
	}
	sum := md5.Sum([]byte(strings.Join(parts, string(rune(31)))))
	return hex.EncodeToString(sum[:])
}
