package queries

import (
	"bssapp-backend/db"
	"bssapp-backend/models"
	"context"
	"database/sql"
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"

	"github.com/lib/pq"
)

type ProductPricingFilters struct {
	Search       string
	ProductCode  []string
	BrandGroup   []string
	AskiliYan    []string
	Kategori     []string
	UrunIlkGrubu []string
	UrunAnaGrubu []string
	UrunAltGrubu []string
	Icerik       []string
	Karisim      []string
	Marka        []string
}

type ProductPricingPage struct {
	Rows       []models.ProductPricing
	TotalCount int
	TotalPages int
	Page       int
	Limit      int
}

func GetAllProductPricingRows(ctx context.Context, chunkSize int, filters ProductPricingFilters, sortBy string, descending bool) ([]models.ProductPricing, error) {
	if chunkSize <= 0 || chunkSize > 1000 {
		chunkSize = 1000
	}

	paramIndex := 1
	args := make([]any, 0, 64)
	nextParam := func() string {
		name := "@p" + strconv.Itoa(paramIndex)
		paramIndex++
		return name
	}
	whereParts := []string{
		"ProductAtt42 IN ('SERI', 'AKSESUAR')",
		"IsBlocked = 0",
		"LEN(LTRIM(RTRIM(ProductCode))) = 13",
	}
	addInFilter := func(expr string, values []string) {
		clean := make([]string, 0, len(values))
		for _, v := range values {
			v = strings.TrimSpace(v)
			if v == "" {
				continue
			}
			clean = append(clean, v)
		}
		if len(clean) == 0 {
			return
		}
		ors := make([]string, 0, len(clean))
		for _, v := range clean {
			p := nextParam()
			ors = append(ors, expr+" = "+p)
			args = append(args, v)
		}
		whereParts = append(whereParts, "("+strings.Join(ors, " OR ")+")")
	}
	brandGroupExpr := `CASE ABS(CHECKSUM(LTRIM(RTRIM(ProductCode)))) % 3
		WHEN 0 THEN 'MARKA GRUBU A'
		WHEN 1 THEN 'MARKA GRUBU B'
		ELSE 'MARKA GRUBU C'
	END`
	addInFilter("LTRIM(RTRIM(ProductCode))", filters.ProductCode)
	addInFilter(brandGroupExpr, filters.BrandGroup)
	addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '')", filters.AskiliYan)
	addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '')", filters.Kategori)
	addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '')", filters.UrunIlkGrubu)
	addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '')", filters.UrunAnaGrubu)
	addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '')", filters.UrunAltGrubu)
	addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '')", filters.Icerik)
	addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '')", filters.Karisim)
	addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '')", filters.Marka)
	if q := strings.TrimSpace(filters.Search); q != "" {
		p := nextParam()
		args = append(args, "%"+q+"%")
		whereParts = append(whereParts, "("+strings.Join([]string{
			"LTRIM(RTRIM(ProductCode)) LIKE " + p,
			"COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '') LIKE " + p,
			"COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '') LIKE " + p,
			"COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '') LIKE " + p,
			"COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '') LIKE " + p,
			"COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '') LIKE " + p,
			"COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '') LIKE " + p,
			"COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '') LIKE " + p,
			"COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '') LIKE " + p,
		}, " OR ")+")")
	}
	whereSQL := strings.Join(whereParts, " AND ")

	sortBy = strings.TrimSpace(sortBy)
	orderDir := "DESC"
	if !descending {
		orderDir = "ASC"
	}
	orderExpr := "CAST(ROUND(ISNULL(sb.InventoryQty1, 0) - ISNULL(pb.PickingQty1, 0) - ISNULL(rb.ReserveQty1, 0) - ISNULL(db.DispOrderQty1, 0), 2) AS DECIMAL(18, 2))"
	if sortBy == "productCode" {
		orderExpr = "rc.ProductCode"
		orderDir = "ASC"
	}
	orderBySQL := orderExpr + ` ` + orderDir
	if !strings.EqualFold(strings.TrimSpace(orderExpr), "rc.ProductCode") {
		orderBySQL += `,
			rc.ProductCode ASC`
	}

	baseQuery := `
		IF OBJECT_ID('tempdb..#req_codes') IS NOT NULL DROP TABLE #req_codes;
		IF OBJECT_ID('tempdb..#stock_base') IS NOT NULL DROP TABLE #stock_base;
		IF OBJECT_ID('tempdb..#pick_base') IS NOT NULL DROP TABLE #pick_base;
		IF OBJECT_ID('tempdb..#reserve_base') IS NOT NULL DROP TABLE #reserve_base;
		IF OBJECT_ID('tempdb..#disp_base') IS NOT NULL DROP TABLE #disp_base;

		SELECT
			f.ProductCode,
			MAX(f.BrandGroupSec) AS BrandGroupSec,
			MAX(f.AskiliYan) AS AskiliYan,
			MAX(f.Kategori) AS Kategori,
			MAX(f.UrunIlkGrubu) AS UrunIlkGrubu,
			MAX(f.UrunAnaGrubu) AS UrunAnaGrubu,
			MAX(f.UrunAltGrubu) AS UrunAltGrubu,
			MAX(f.Icerik) AS Icerik,
			MAX(f.Karisim) AS Karisim,
			MAX(f.Marka) AS Marka,
			MAX(f.BrandCode) AS BrandCode
		INTO #req_codes
		FROM (
			SELECT
				LTRIM(RTRIM(ProductCode)) AS ProductCode,
				` + brandGroupExpr + ` AS BrandGroupSec,
				COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '') AS AskiliYan,
				COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '') AS Kategori,
				COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '') AS UrunIlkGrubu,
				COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '') AS UrunAnaGrubu,
				COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '') AS UrunAltGrubu,
				COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '') AS Icerik,
				COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '') AS Karisim,
				COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '') AS Marka,
				COALESCE(LTRIM(RTRIM(ProductAtt10)), '') AS BrandCode
			FROM ProductFilterWithDescription('TR')
			WHERE ` + whereSQL + `
		) f
		GROUP BY f.ProductCode;

		CREATE CLUSTERED INDEX IX_req_codes_ProductCode ON #req_codes(ProductCode);

		SELECT
			s.ItemCode AS ItemCode,
			SUM(s.In_Qty1 - s.Out_Qty1) AS InventoryQty1
		INTO #stock_base
		FROM trStock s
		INNER JOIN #req_codes rc
			ON rc.ProductCode = s.ItemCode
		WHERE s.ItemTypeCode = 1
		  AND LEN(s.ItemCode) = 13
		GROUP BY s.ItemCode;

		CREATE CLUSTERED INDEX IX_stock_base_ItemCode ON #stock_base(ItemCode);

		SELECT
			p.ItemCode AS ItemCode,
			SUM(p.Qty1) AS PickingQty1
		INTO #pick_base
		FROM PickingStates p
		INNER JOIN #req_codes rc
			ON rc.ProductCode = p.ItemCode
		WHERE p.ItemTypeCode = 1
		  AND LEN(p.ItemCode) = 13
		GROUP BY p.ItemCode;

		SELECT
			r.ItemCode AS ItemCode,
			SUM(r.Qty1) AS ReserveQty1
		INTO #reserve_base
		FROM ReserveStates r
		INNER JOIN #req_codes rc
			ON rc.ProductCode = r.ItemCode
		WHERE r.ItemTypeCode = 1
		  AND LEN(r.ItemCode) = 13
		GROUP BY r.ItemCode;

		SELECT
			d.ItemCode AS ItemCode,
			SUM(d.Qty1) AS DispOrderQty1
		INTO #disp_base
		FROM DispOrderStates d
		INNER JOIN #req_codes rc
			ON rc.ProductCode = d.ItemCode
		WHERE d.ItemTypeCode = 1
		  AND LEN(d.ItemCode) = 13
		GROUP BY d.ItemCode;

		SELECT
			rc.ProductCode,
			rc.BrandGroupSec,
			rc.AskiliYan,
			rc.Kategori,
			rc.UrunIlkGrubu,
			rc.UrunAnaGrubu,
			rc.UrunAltGrubu,
			rc.Icerik,
			rc.Karisim,
			rc.Marka,
			rc.BrandCode,
			CAST(ROUND(
				ISNULL(sb.InventoryQty1, 0)
				- ISNULL(pb.PickingQty1, 0)
				- ISNULL(rb.ReserveQty1, 0)
				- ISNULL(db.DispOrderQty1, 0)
			, 2) AS DECIMAL(18, 2)) AS StockQty
		FROM #req_codes rc
		LEFT JOIN #stock_base sb
			ON sb.ItemCode = rc.ProductCode
		LEFT JOIN #pick_base pb
			ON pb.ItemCode = rc.ProductCode
		LEFT JOIN #reserve_base rb
			ON rb.ItemCode = rc.ProductCode
		LEFT JOIN #disp_base db
			ON db.ItemCode = rc.ProductCode
		ORDER BY
			` + orderBySQL + `;
	`

	rows, err := db.MssqlDB.QueryContext(ctx, baseQuery, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	out := make([]models.ProductPricing, 0, 2048)
	for rows.Next() {
		var item models.ProductPricing
		if err := rows.Scan(
			&item.ProductCode,
			&item.BrandGroupSec,
			&item.AskiliYan,
			&item.Kategori,
			&item.UrunIlkGrubu,
			&item.UrunAnaGrubu,
			&item.UrunAltGrubu,
			&item.Icerik,
			&item.Karisim,
			&item.Marka,
			&item.BrandCode,
			&item.StockQty,
		); err != nil {
			return nil, err
		}
		out = append(out, item)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	if len(out) == 0 {
		return out, nil
	}

	if err := enrichAllProductPricingRows(ctx, out, chunkSize); err != nil {
		return nil, err
	}

	return out, nil
}

func enrichAllProductPricingRows(ctx context.Context, out []models.ProductPricing, chunkSize int) error {
	if len(out) == 0 {
		return nil
	}
	if chunkSize <= 0 || chunkSize > 1000 {
		chunkSize = 1000
	}

	indexByCode := make(map[string]int, len(out))
	codes := make([]string, 0, len(out))
	for i := range out {
		code := strings.TrimSpace(out[i].ProductCode)
		if code == "" {
			continue
		}
		indexByCode[code] = i
		codes = append(codes, code)
	}

	for _, chunk := range chunkStringSlice(codes, chunkSize) {
		valueRows := make([]string, 0, len(chunk))
		metricArgs := make([]any, 0, len(chunk))
		for i, code := range chunk {
			paramName := "@p" + strconv.Itoa(i+1)
			valueRows = append(valueRows, "("+paramName+")")
			metricArgs = append(metricArgs, code)
		}

		metricsQuery := `
			WITH req_codes AS (
				SELECT DISTINCT LTRIM(RTRIM(v.ProductCode)) AS ProductCode
				FROM (VALUES ` + strings.Join(valueRows, ",") + `) v(ProductCode)
				WHERE LEN(LTRIM(RTRIM(v.ProductCode))) > 0
			),
			latest_pricelist_line AS (
				SELECT
					p.ItemCode AS ItemCode,
					LTRIM(RTRIM(p.DocCurrencyCode)) AS DocCurrencyCode,
					CAST(p.Price AS DECIMAL(18, 2)) AS Price,
					ROW_NUMBER() OVER (
						PARTITION BY p.ItemCode, LTRIM(RTRIM(p.DocCurrencyCode))
						ORDER BY p.ValidDate DESC, p.ValidTime DESC, p.LastUpdatedDate DESC
					) AS rn
				FROM dbo.trPriceListLine p
				INNER JOIN req_codes rc
					ON rc.ProductCode = p.ItemCode
				WHERE p.ItemTypeCode = 1
				  AND ISNULL(p.IsDisabled, 0) = 0
				  AND LTRIM(RTRIM(p.DocCurrencyCode)) IN ('USD', 'TRY')
				  AND (
						(LTRIM(RTRIM(p.DocCurrencyCode)) = 'USD' AND LTRIM(RTRIM(p.PriceGroupCode)) = 'TM-USD')
					 OR (LTRIM(RTRIM(p.DocCurrencyCode)) = 'TRY' AND LTRIM(RTRIM(p.PriceGroupCode)) = 'TM-TRY')
				  )
				  AND p.Price IS NOT NULL
				  AND p.Price > 0
			),
			base_prices AS (
				SELECT
					ItemCode,
					MAX(CASE WHEN DocCurrencyCode = 'USD' THEN Price END) AS BasePriceUsd,
					MAX(CASE WHEN DocCurrencyCode = 'TRY' THEN Price END) AS BasePriceTry
				FROM latest_pricelist_line
				WHERE rn = 1
				GROUP BY ItemCode
			),
			latest_base_price AS (
				SELECT
					b.ItemCode AS ItemCode,
					CAST(b.Price AS DECIMAL(18, 2)) AS CostPrice,
					CONVERT(VARCHAR(10), b.PriceDate, 23) AS LastPricingDate,
					ROW_NUMBER() OVER (
						PARTITION BY b.ItemCode
						ORDER BY b.PriceDate DESC, b.LastUpdatedDate DESC
					) AS rn
				FROM prItemBasePrice b
				INNER JOIN req_codes rc
					ON rc.ProductCode = b.ItemCode
				WHERE b.ItemTypeCode = 1
				  AND b.BasePriceCode = 1
				  AND LTRIM(RTRIM(b.CurrencyCode)) = 'USD'
			),
			stock_entry_dates AS (
				SELECT
					s.ItemCode AS ItemCode,
					CONVERT(VARCHAR(10), MAX(s.OperationDate), 23) AS StockEntryDate
				FROM trStock s
				INNER JOIN req_codes rc
					ON rc.ProductCode = s.ItemCode
				WHERE s.ItemTypeCode = 1
				  AND LEN(s.ItemCode) = 13
				  AND s.In_Qty1 > 0
				  AND LTRIM(RTRIM(s.InnerProcessCode)) = 'OP'
				  AND LTRIM(RTRIM(s.WarehouseCode)) IN (
					'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
					'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
					'1-0-33','101','1-014','1-0-49','1-0-36'
				  )
				GROUP BY s.ItemCode
			)
			SELECT
				rc.ProductCode,
				COALESCE(lp.CostPrice, 0) AS CostPrice,
				COALESCE(bp.BasePriceUsd, 0) AS BasePriceUsd,
				COALESCE(bp.BasePriceTry, 0) AS BasePriceTry,
				COALESCE(se.StockEntryDate, '') AS StockEntryDate,
				COALESCE(lp.LastPricingDate, '') AS LastPricingDate
			FROM req_codes rc
			LEFT JOIN latest_base_price lp
				ON lp.ItemCode = rc.ProductCode
			   AND lp.rn = 1
			LEFT JOIN base_prices bp
				ON bp.ItemCode = rc.ProductCode
			LEFT JOIN stock_entry_dates se
				ON se.ItemCode = rc.ProductCode;
		`

		metricRows, err := db.MssqlDB.QueryContext(ctx, metricsQuery, metricArgs...)
		if err != nil {
			return fmt.Errorf("metrics query failed: %w", err)
		}
		for metricRows.Next() {
			var code string
			var costPrice, basePriceUsd, basePriceTry float64
			var stockEntryDate, lastPricingDate string
			if err := metricRows.Scan(&code, &costPrice, &basePriceUsd, &basePriceTry, &stockEntryDate, &lastPricingDate); err != nil {
				_ = metricRows.Close()
				return err
			}
			if idx, ok := indexByCode[strings.TrimSpace(code)]; ok {
				out[idx].CostPrice = costPrice
				out[idx].BasePriceUsd = basePriceUsd
				out[idx].BasePriceTry = basePriceTry
				out[idx].StockEntryDate = stockEntryDate
				out[idx].LastPricingDate = lastPricingDate
			}
		}
		if err := metricRows.Err(); err != nil {
			_ = metricRows.Close()
			return err
		}
		_ = metricRows.Close()

		if uretimDB := db.GetUretimDB(); uretimDB != nil {
			costingQuery := `
				WITH req_codes AS (
					SELECT DISTINCT LTRIM(RTRIM(v.ProductCode)) AS ProductCode
					FROM (VALUES ` + strings.Join(valueRows, ",") + `) v(ProductCode)
					WHERE LEN(LTRIM(RTRIM(v.ProductCode))) > 0
				)
				SELECT
					LTRIM(RTRIM(m.UrunKodu)) AS UrunKodu,
					CONVERT(VARCHAR(10), MAX(m.Tarihi), 23) AS LastCostingDate
				FROM dbo.spUrtOnMLMas m
				INNER JOIN req_codes rc
					ON rc.ProductCode = LTRIM(RTRIM(m.UrunKodu))
				GROUP BY LTRIM(RTRIM(m.UrunKodu));
			`
			costRows, err := uretimDB.QueryContext(ctx, costingQuery, metricArgs...)
			if err == nil {
				for costRows.Next() {
					var code, d string
					if err := costRows.Scan(&code, &d); err != nil {
						_ = costRows.Close()
						return err
					}
					if idx, ok := indexByCode[strings.TrimSpace(code)]; ok && strings.TrimSpace(d) != "" {
						out[idx].LastCostingDate = strings.TrimSpace(d)
					}
				}
				if err := costRows.Err(); err != nil {
					_ = costRows.Close()
					return err
				}
				_ = costRows.Close()
			}
		}

		if pg := db.PgDB; pg != nil {
			tierSQL := `
				WITH ranked AS (
					SELECT
						mmitem.code AS code,
						sdprc.sdprcgrp_id AS grp,
						sdprc.crn AS crn,
						COALESCE(sdprc.prc, 0) AS prc,
						ROW_NUMBER() OVER (
							PARTITION BY mmitem.code, sdprc.crn, sdprc.sdprcgrp_id
							ORDER BY sdprc.zlins_dttm DESC
						) AS rn
					FROM sdprc
					JOIN mmitem ON mmitem.id = sdprc.mmitem_id
					WHERE mmitem.code = ANY($1)
					  AND sdprc.sdprcgrp_id BETWEEN 1 AND 6
					  AND sdprc.crn IN ('USD', 'EUR', 'TRY')
					  AND sdprc.prc IS NOT NULL
					  AND sdprc.prc > 0
				)
				SELECT code, grp, crn, prc
				FROM ranked
				WHERE rn = 1;
			`
			pgRows, err := pg.QueryContext(ctx, tierSQL, pq.Array(chunk))
			if err == nil {
				for pgRows.Next() {
					var code, crn string
					var grp int
					var prc float64
					if err := pgRows.Scan(&code, &grp, &crn, &prc); err != nil {
						_ = pgRows.Close()
						return err
					}
					idx, ok := indexByCode[strings.TrimSpace(code)]
					if !ok {
						continue
					}
					switch strings.ToUpper(strings.TrimSpace(crn)) {
					case "USD":
						switch grp {
						case 1:
							out[idx].USD1 = prc
						case 2:
							out[idx].USD2 = prc
						case 3:
							out[idx].USD3 = prc
						case 4:
							out[idx].USD4 = prc
						case 5:
							out[idx].USD5 = prc
						case 6:
							out[idx].USD6 = prc
						}
					case "EUR":
						switch grp {
						case 1:
							out[idx].EUR1 = prc
						case 2:
							out[idx].EUR2 = prc
						case 3:
							out[idx].EUR3 = prc
						case 4:
							out[idx].EUR4 = prc
						case 5:
							out[idx].EUR5 = prc
						case 6:
							out[idx].EUR6 = prc
						}
					case "TRY":
						switch grp {
						case 1:
							out[idx].TRY1 = prc
						case 2:
							out[idx].TRY2 = prc
						case 3:
							out[idx].TRY3 = prc
						case 4:
							out[idx].TRY4 = prc
						case 5:
							out[idx].TRY5 = prc
						case 6:
							out[idx].TRY6 = prc
						}
					}
				}
				if err := pgRows.Err(); err != nil {
					_ = pgRows.Close()
					return err
				}
				_ = pgRows.Close()
			}

			// Last pricing date should reflect the most recent price publish, not the Nebim base price date.
			// E-commerce reads pg.sdprc; use its latest write timestamp as the authoritative "last pricing" signal.
			dateRows, err := pg.QueryContext(ctx, `
SELECT
  mmitem.code AS code,
  to_char(MAX(sdprc.zlins_dttm), 'YYYY-MM-DD') AS last_pricing_date
FROM sdprc
JOIN mmitem ON mmitem.id = sdprc.mmitem_id
WHERE mmitem.code = ANY($1)
GROUP BY mmitem.code;
`, pq.Array(chunk))
			if err == nil {
				for dateRows.Next() {
					var code, ymd string
					if err := dateRows.Scan(&code, &ymd); err != nil {
						_ = dateRows.Close()
						return err
					}
					code = strings.TrimSpace(code)
					ymd = strings.TrimSpace(ymd)
					if code == "" || len(ymd) != 10 {
						continue
					}
					if idx, ok := indexByCode[code]; ok {
						cur := strings.TrimSpace(out[idx].LastPricingDate)
						// both are YYYY-MM-DD, lexicographical compare is safe
						if cur == "" || cur < ymd {
							out[idx].LastPricingDate = ymd
						}
					}
				}
				if err := dateRows.Err(); err != nil {
					_ = dateRows.Close()
					return err
				}
				_ = dateRows.Close()
			}
		}
	}

	if pg := db.PgDB; pg != nil {
		brandCodes := make([]string, 0, len(out))
		seen := make(map[string]struct{}, len(out))
		for _, it := range out {
			code := strings.TrimSpace(it.BrandCode)
			if code == "" {
				continue
			}
			if _, ok := seen[code]; ok {
				continue
			}
			seen[code] = struct{}{}
			brandCodes = append(brandCodes, code)
		}
		for _, chunk := range chunkStringSlice(brandCodes, chunkSize) {
			rows, err := pg.QueryContext(ctx, `
SELECT
  m.brand_code,
  COALESCE(g.title, '') AS grp_title
FROM mk_brandgrpmatch m
JOIN mk_brandgrp g ON g.id = m.grp_id
WHERE m.brand_code = ANY($1)
`, pq.Array(chunk))
			if err != nil {
				continue
			}
			grpByBrand := make(map[string]string, len(chunk))
			for rows.Next() {
				var code, title string
				if err := rows.Scan(&code, &title); err != nil {
					_ = rows.Close()
					return err
				}
				grpByBrand[strings.TrimSpace(code)] = strings.TrimSpace(title)
			}
			if err := rows.Err(); err != nil {
				_ = rows.Close()
				return err
			}
			_ = rows.Close()
			for i := range out {
				if title, ok := grpByBrand[strings.TrimSpace(out[i].BrandCode)]; ok {
					out[i].BrandGroupSec = title
				}
			}
		}
	}

	return nil
}

func chunkStringSlice(values []string, size int) [][]string {
	if size <= 0 {
		size = 1000
	}
	out := make([][]string, 0, (len(values)+size-1)/size)
	for start := 0; start < len(values); start += size {
		end := start + size
		if end > len(values) {
			end = len(values)
		}
		out = append(out, values[start:end])
	}
	return out
}

func cleanProductPricingFilterValues(values []string) []string {
	clean := make([]string, 0, len(values))
	seen := make(map[string]struct{}, len(values))
	for _, v := range values {
		v = strings.TrimSpace(v)
		if v == "" {
			continue
		}
		if _, ok := seen[v]; ok {
			continue
		}
		seen[v] = struct{}{}
		clean = append(clean, v)
	}
	return clean
}

func isProductCodeOnlyPricingFilter(filters ProductPricingFilters) bool {
	return len(cleanProductPricingFilterValues(filters.ProductCode)) > 0 &&
		strings.TrimSpace(filters.Search) == "" &&
		len(cleanProductPricingFilterValues(filters.BrandGroup)) == 0 &&
		len(cleanProductPricingFilterValues(filters.AskiliYan)) == 0 &&
		len(cleanProductPricingFilterValues(filters.Kategori)) == 0 &&
		len(cleanProductPricingFilterValues(filters.UrunIlkGrubu)) == 0 &&
		len(cleanProductPricingFilterValues(filters.UrunAnaGrubu)) == 0 &&
		len(cleanProductPricingFilterValues(filters.UrunAltGrubu)) == 0 &&
		len(cleanProductPricingFilterValues(filters.Icerik)) == 0 &&
		len(cleanProductPricingFilterValues(filters.Karisim)) == 0 &&
		len(cleanProductPricingFilterValues(filters.Marka)) == 0
}

func GetProductPricingPage(ctx context.Context, page int, limit int, filters ProductPricingFilters, includeTotal bool, sortBy string, descending bool) (ProductPricingPage, error) {
	result := ProductPricingPage{
		Rows:       []models.ProductPricing{},
		TotalCount: 0,
		TotalPages: 0,
		Page:       page,
		Limit:      limit,
	}
	if page <= 0 {
		page = 1
	}
	if limit <= 0 {
		limit = 500
	}
	offset := (page - 1) * limit

	paramIndex := 1
	args := make([]any, 0, 64)
	nextParam := func() string {
		name := "@p" + strconv.Itoa(paramIndex)
		paramIndex++
		return name
	}
	whereParts := []string{
		"ProductAtt42 IN ('SERI', 'AKSESUAR')",
		"IsBlocked = 0",
		"LEN(LTRIM(RTRIM(ProductCode))) = 13",
	}
	addInFilter := func(expr string, values []string) {
		clean := make([]string, 0, len(values))
		for _, v := range values {
			v = strings.TrimSpace(v)
			if v == "" {
				continue
			}
			clean = append(clean, v)
		}
		if len(clean) == 0 {
			return
		}
		ors := make([]string, 0, len(clean))
		for _, v := range clean {
			p := nextParam()
			ors = append(ors, expr+" = "+p)
			args = append(args, v)
		}
		whereParts = append(whereParts, "("+strings.Join(ors, " OR ")+")")
	}
	brandGroupExpr := `CASE ABS(CHECKSUM(LTRIM(RTRIM(ProductCode)))) % 3
		WHEN 0 THEN 'MARKA GRUBU A'
		WHEN 1 THEN 'MARKA GRUBU B'
		ELSE 'MARKA GRUBU C'
	END`
	addInFilter("LTRIM(RTRIM(ProductCode))", filters.ProductCode)
	addInFilter(brandGroupExpr, filters.BrandGroup)
	addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '')", filters.AskiliYan)
	addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '')", filters.Kategori)
	addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '')", filters.UrunIlkGrubu)
	addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '')", filters.UrunAnaGrubu)
	addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '')", filters.UrunAltGrubu)
	addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '')", filters.Icerik)
	addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '')", filters.Karisim)
	addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '')", filters.Marka)
	if q := strings.TrimSpace(filters.Search); q != "" {
		p := nextParam()
		args = append(args, "%"+q+"%")
		whereParts = append(whereParts, "("+strings.Join([]string{
			"LTRIM(RTRIM(ProductCode)) LIKE " + p,
			"COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '') LIKE " + p,
			"COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '') LIKE " + p,
			"COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '') LIKE " + p,
			"COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '') LIKE " + p,
			"COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '') LIKE " + p,
			"COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '') LIKE " + p,
			"COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '') LIKE " + p,
			"COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '') LIKE " + p,
		}, " OR ")+")")
	}
	whereSQL := strings.Join(whereParts, " AND ")
	cleanProductCodes := cleanProductPricingFilterValues(filters.ProductCode)
	productCodeOnlyFilter := isProductCodeOnlyPricingFilter(filters)

	if includeTotal && productCodeOnlyFilter {
		result.TotalCount = len(cleanProductCodes)
		if result.TotalCount == 0 {
			result.TotalPages = 0
			result.Page = 1
			return result, nil
		}
		totalPages := int(math.Ceil(float64(result.TotalCount) / float64(limit)))
		if totalPages <= 0 {
			totalPages = 1
		}
		if page > totalPages {
			page = totalPages
			offset = (page - 1) * limit
		}
		result.Page = page
		result.Limit = limit
		result.TotalPages = totalPages
	} else if includeTotal {
		countQuery := ` 
			SELECT COUNT(DISTINCT LTRIM(RTRIM(ProductCode))) 
			FROM ProductFilterWithDescription('TR') 
			WHERE ` + whereSQL + `; 
		`
		var totalCount int
		if err := db.MssqlDB.QueryRowContext(ctx, countQuery, args...).Scan(&totalCount); err != nil {
			return result, err
		}
		result.TotalCount = totalCount
		if totalCount == 0 {
			result.TotalPages = 0
			result.Page = 1
			return result, nil
		}
		totalPages := int(math.Ceil(float64(totalCount) / float64(limit)))
		if totalPages <= 0 {
			totalPages = 1
		}
		if page > totalPages {
			page = totalPages
			offset = (page - 1) * limit
		}
		result.Page = page
		result.Limit = limit
		result.TotalPages = totalPages
	} else {
		// Skip COUNT(*) for performance; client will infer hasMore from page size.
		result.TotalCount = 0
		result.TotalPages = 0
		result.Page = page
		result.Limit = limit
	}

	// Stage 1: fetch only paged products first. Exact product-code filters do not
	// need the stock sort temp table here; detailed metrics are fetched below.
	productCodeFastPath := len(cleanProductCodes) > 0
	sortBy = strings.TrimSpace(sortBy)
	orderDir := "DESC"
	if !descending {
		orderDir = "ASC"
	}
	// Only allow a small safe list.
	orderExpr := "CAST(ROUND(ISNULL(sb.InventoryQty1, 0), 2) AS DECIMAL(18, 2))"
	if sortBy == "productCode" {
		orderExpr = "rc.ProductCode"
		orderDir = "ASC"
	}
	orderBySQL := orderExpr + ` ` + orderDir
	if !strings.EqualFold(strings.TrimSpace(orderExpr), "rc.ProductCode") {
		orderBySQL += `,
			rc.ProductCode ASC`
	}
	productQuery := `
		IF OBJECT_ID('tempdb..#req_codes') IS NOT NULL DROP TABLE #req_codes;
		IF OBJECT_ID('tempdb..#stock_base') IS NOT NULL DROP TABLE #stock_base;

		SELECT
			f.ProductCode,
			MAX(f.BrandGroupSec) AS BrandGroupSec,
			MAX(f.AskiliYan) AS AskiliYan,
			MAX(f.Kategori) AS Kategori,
			MAX(f.UrunIlkGrubu) AS UrunIlkGrubu,
			MAX(f.UrunAnaGrubu) AS UrunAnaGrubu,
			MAX(f.UrunAltGrubu) AS UrunAltGrubu,
			MAX(f.Icerik) AS Icerik,
			MAX(f.Karisim) AS Karisim,
			MAX(f.Marka) AS Marka,
			MAX(f.BrandCode) AS BrandCode
		INTO #req_codes
		FROM (
			SELECT
				LTRIM(RTRIM(ProductCode)) AS ProductCode,
				` + brandGroupExpr + ` AS BrandGroupSec,
				COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '') AS AskiliYan,
				COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '') AS Kategori,
				COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '') AS UrunIlkGrubu,
				COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '') AS UrunAnaGrubu,
				COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '') AS UrunAltGrubu,
				COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '') AS Icerik,
				COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '') AS Karisim,
				COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '') AS Marka,
				COALESCE(LTRIM(RTRIM(ProductAtt10)), '') AS BrandCode
			FROM ProductFilterWithDescription('TR')
			WHERE ` + whereSQL + `
		) f
		GROUP BY f.ProductCode;

		CREATE CLUSTERED INDEX IX_req_codes_ProductCode ON #req_codes(ProductCode);

		SELECT
			s.ItemCode AS ItemCode,
			SUM(s.In_Qty1 - s.Out_Qty1) AS InventoryQty1
		INTO #stock_base
		FROM trStock s
		INNER JOIN #req_codes rc
			ON rc.ProductCode = s.ItemCode
		WHERE s.ItemTypeCode = 1
		  AND LEN(s.ItemCode) = 13
		GROUP BY s.ItemCode;

		CREATE CLUSTERED INDEX IX_stock_base_ItemCode ON #stock_base(ItemCode);

		SELECT
			rc.ProductCode,
			rc.BrandGroupSec,
			rc.AskiliYan,
			rc.Kategori,
			rc.UrunIlkGrubu,
			rc.UrunAnaGrubu,
			rc.UrunAltGrubu,
			rc.Icerik,
			rc.Karisim,
			rc.Marka,
			rc.BrandCode
		FROM #req_codes rc
		LEFT JOIN #stock_base sb
			ON sb.ItemCode = rc.ProductCode
		ORDER BY
			` + orderBySQL + `
		OFFSET ` + strconv.Itoa(offset) + ` ROWS
		FETCH NEXT ` + strconv.Itoa(limit) + ` ROWS ONLY;
	`
	if productCodeOnlyFilter {
		valueRows := make([]string, 0, len(cleanProductCodes))
		directArgs := make([]any, 0, len(cleanProductCodes))
		for i, code := range cleanProductCodes {
			paramName := "@p" + strconv.Itoa(i+1)
			valueRows = append(valueRows, "("+paramName+")")
			directArgs = append(directArgs, code)
		}
		args = directArgs
		productQuery = `
			WITH req_codes AS (
				SELECT DISTINCT LTRIM(RTRIM(v.ProductCode)) AS ProductCode
				FROM (VALUES ` + strings.Join(valueRows, ",") + `) v(ProductCode)
				WHERE LEN(LTRIM(RTRIM(v.ProductCode))) > 0
			),
			attr AS (
				SELECT
					a.ItemCode AS ProductCode,
					MAX(CASE WHEN a.AttributeTypeCode = 45 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS AskiliYan,
					MAX(CASE WHEN a.AttributeTypeCode = 44 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Kategori,
					MAX(CASE WHEN a.AttributeTypeCode = 42 THEN LTRIM(RTRIM(a.AttributeCode)) ELSE '' END) AS UrunIlkGrubuCode,
					MAX(CASE WHEN a.AttributeTypeCode = 42 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS UrunIlkGrubu,
					MAX(CASE WHEN a.AttributeTypeCode = 1 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS UrunAnaGrubu,
					MAX(CASE WHEN a.AttributeTypeCode = 2 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS UrunAltGrubu,
					MAX(CASE WHEN a.AttributeTypeCode = 41 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Icerik,
					MAX(CASE WHEN a.AttributeTypeCode = 29 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Karisim,
					MAX(CASE WHEN a.AttributeTypeCode = 10 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Marka,
					MAX(CASE WHEN a.AttributeTypeCode = 10 THEN LTRIM(RTRIM(a.AttributeCode)) ELSE '' END) AS BrandCode
				FROM dbo.prItemAttribute a
				INNER JOIN req_codes rc
					ON rc.ProductCode = a.ItemCode
				LEFT JOIN dbo.cdItemAttributeDesc d
					ON d.ItemTypeCode = a.ItemTypeCode
				   AND d.AttributeTypeCode = a.AttributeTypeCode
				   AND d.AttributeCode = a.AttributeCode
				   AND d.LangCode = 'TR'
				WHERE a.ItemTypeCode = 1
				  AND a.AttributeTypeCode IN (1,2,10,29,41,42,44,45)
				GROUP BY a.ItemCode
			)
			SELECT
				rc.ProductCode,
				CAST('' AS NVARCHAR(100)) AS BrandGroupSec,
				COALESCE(attr.AskiliYan, '') AS AskiliYan,
				COALESCE(attr.Kategori, '') AS Kategori,
				COALESCE(attr.UrunIlkGrubu, '') AS UrunIlkGrubu,
				COALESCE(attr.UrunAnaGrubu, '') AS UrunAnaGrubu,
				COALESCE(attr.UrunAltGrubu, '') AS UrunAltGrubu,
				COALESCE(attr.Icerik, '') AS Icerik,
				COALESCE(attr.Karisim, '') AS Karisim,
				COALESCE(attr.Marka, '') AS Marka,
				COALESCE(attr.BrandCode, '') AS BrandCode
			FROM req_codes rc
			INNER JOIN dbo.cdItem ci
				ON ci.ItemTypeCode = 1
			   AND ci.ItemCode = rc.ProductCode
			LEFT JOIN attr
				ON attr.ProductCode = rc.ProductCode
			WHERE ISNULL(ci.IsBlocked, 0) = 0
			  AND LEN(ci.ItemCode) = 13
			  AND COALESCE(attr.UrunIlkGrubuCode, '') IN ('SERI', 'AKSESUAR')
			ORDER BY rc.ProductCode ASC
			OFFSET ` + strconv.Itoa(offset) + ` ROWS
			FETCH NEXT ` + strconv.Itoa(limit) + ` ROWS ONLY;
		`
	} else if productCodeFastPath {
		productQuery = `
			IF OBJECT_ID('tempdb..#req_codes') IS NOT NULL DROP TABLE #req_codes;

			SELECT
				f.ProductCode,
				MAX(f.BrandGroupSec) AS BrandGroupSec,
				MAX(f.AskiliYan) AS AskiliYan,
				MAX(f.Kategori) AS Kategori,
				MAX(f.UrunIlkGrubu) AS UrunIlkGrubu,
				MAX(f.UrunAnaGrubu) AS UrunAnaGrubu,
				MAX(f.UrunAltGrubu) AS UrunAltGrubu,
				MAX(f.Icerik) AS Icerik,
				MAX(f.Karisim) AS Karisim,
				MAX(f.Marka) AS Marka,
				MAX(f.BrandCode) AS BrandCode
			INTO #req_codes
			FROM (
				SELECT
					LTRIM(RTRIM(ProductCode)) AS ProductCode,
					` + brandGroupExpr + ` AS BrandGroupSec,
					COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '') AS AskiliYan,
					COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '') AS Kategori,
					COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '') AS UrunIlkGrubu,
					COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '') AS UrunAnaGrubu,
					COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '') AS UrunAltGrubu,
					COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '') AS Icerik,
					COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '') AS Karisim,
					COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '') AS Marka,
					COALESCE(LTRIM(RTRIM(ProductAtt10)), '') AS BrandCode
				FROM ProductFilterWithDescription('TR')
				WHERE ` + whereSQL + `
			) f
			GROUP BY f.ProductCode;

			CREATE CLUSTERED INDEX IX_req_codes_ProductCode ON #req_codes(ProductCode);

			SELECT
				rc.ProductCode,
				rc.BrandGroupSec,
				rc.AskiliYan,
				rc.Kategori,
				rc.UrunIlkGrubu,
				rc.UrunAnaGrubu,
				rc.UrunAltGrubu,
				rc.Icerik,
				rc.Karisim,
				rc.Marka,
				rc.BrandCode
			FROM #req_codes rc
			ORDER BY rc.ProductCode ASC
			OFFSET ` + strconv.Itoa(offset) + ` ROWS
			FETCH NEXT ` + strconv.Itoa(limit) + ` ROWS ONLY;
		`
	}

	var (
		rows    *sql.Rows
		rowsErr error
	)
	for attempt := 1; attempt <= 3; attempt++ {
		var err error
		rows, err = db.MssqlDB.QueryContext(ctx, productQuery, args...)
		if err == nil {
			rowsErr = nil
			break
		}
		rowsErr = err
		if ctx.Err() != nil || !isTransientMSSQLNetworkError(err) || attempt == 3 {
			break
		}
		wait := time.Duration(attempt*300) * time.Millisecond
		select {
		case <-ctx.Done():
			break
		case <-time.After(wait):
		}
	}
	if rowsErr != nil {
		return result, rowsErr
	}
	defer rows.Close()

	out := make([]models.ProductPricing, 0, limit)
	for rows.Next() {
		var item models.ProductPricing
		if err := rows.Scan(
			&item.ProductCode,
			&item.BrandGroupSec,
			&item.AskiliYan,
			&item.Kategori,
			&item.UrunIlkGrubu,
			&item.UrunAnaGrubu,
			&item.UrunAltGrubu,
			&item.Icerik,
			&item.Karisim,
			&item.Marka,
			&item.BrandCode,
		); err != nil {
			return result, err
		}
		out = append(out, item)
	}
	if err := rows.Err(); err != nil {
		return result, err
	}
	if len(out) == 0 {
		result.Rows = out
		return result, nil
	}

	// Stage 2: fetch metrics only for paged product codes.
	codes := make([]string, 0, len(out))
	for _, item := range out {
		codes = append(codes, strings.TrimSpace(item.ProductCode))
	}
	valueRows := make([]string, 0, len(codes))
	metricArgs := make([]any, 0, len(codes))
	for i, code := range codes {
		paramName := "@p" + strconv.Itoa(i+1)
		valueRows = append(valueRows, "("+paramName+")")
		metricArgs = append(metricArgs, code)
	}

	metricsQuery := `
		WITH req_codes AS (
			SELECT DISTINCT LTRIM(RTRIM(v.ProductCode)) AS ProductCode
			FROM (VALUES ` + strings.Join(valueRows, ",") + `) v(ProductCode)
			WHERE LEN(LTRIM(RTRIM(v.ProductCode))) > 0
		),
		latest_pricelist_line AS (
			-- Base prices from Nebim V3 price lists (trPriceListLine).
			-- Pick the latest record per (ItemCode, Currency) using ValidDate/ValidTime, then LastUpdatedDate.
			SELECT
				p.ItemCode AS ItemCode,
				LTRIM(RTRIM(p.DocCurrencyCode)) AS DocCurrencyCode,
				CAST(p.Price AS DECIMAL(18, 2)) AS Price,
				ROW_NUMBER() OVER (
					PARTITION BY p.ItemCode, LTRIM(RTRIM(p.DocCurrencyCode))
					ORDER BY p.ValidDate DESC, p.ValidTime DESC, p.LastUpdatedDate DESC
				) AS rn
			FROM dbo.trPriceListLine p
			INNER JOIN req_codes rc
				ON rc.ProductCode = p.ItemCode
			WHERE p.ItemTypeCode = 1
			  AND ISNULL(p.IsDisabled, 0) = 0
			  AND LTRIM(RTRIM(p.DocCurrencyCode)) IN ('USD', 'TRY')
			  AND (
					(LTRIM(RTRIM(p.DocCurrencyCode)) = 'USD' AND LTRIM(RTRIM(p.PriceGroupCode)) = 'TM-USD')
				 OR (LTRIM(RTRIM(p.DocCurrencyCode)) = 'TRY' AND LTRIM(RTRIM(p.PriceGroupCode)) = 'TM-TRY')
			  )
			  AND p.Price IS NOT NULL
			  AND p.Price > 0
		),
		base_prices AS (
			SELECT
				ItemCode,
				MAX(CASE WHEN DocCurrencyCode = 'USD' THEN Price END) AS BasePriceUsd,
				MAX(CASE WHEN DocCurrencyCode = 'TRY' THEN Price END) AS BasePriceTry
			FROM latest_pricelist_line
			WHERE rn = 1
			GROUP BY ItemCode
		),
		latest_base_price AS (
			SELECT
				b.ItemCode AS ItemCode,
				CAST(b.Price AS DECIMAL(18, 2)) AS CostPrice,
				CONVERT(VARCHAR(10), b.PriceDate, 23) AS LastPricingDate,
				ROW_NUMBER() OVER (
					PARTITION BY b.ItemCode
					ORDER BY b.PriceDate DESC, b.LastUpdatedDate DESC
				) AS rn
			FROM prItemBasePrice b
			INNER JOIN req_codes rc
				ON rc.ProductCode = b.ItemCode
			WHERE b.ItemTypeCode = 1
			  AND b.BasePriceCode = 1
			  AND LTRIM(RTRIM(b.CurrencyCode)) = 'USD'
		),
		stock_entry_dates AS (
			SELECT
				s.ItemCode AS ItemCode,
				CONVERT(VARCHAR(10), MAX(s.OperationDate), 23) AS StockEntryDate
			FROM trStock s
			INNER JOIN req_codes rc
				ON rc.ProductCode = s.ItemCode
			WHERE s.ItemTypeCode = 1
			  AND LEN(s.ItemCode) = 13
			  AND s.In_Qty1 > 0
			  AND LTRIM(RTRIM(s.InnerProcessCode)) = 'OP'
			  AND LTRIM(RTRIM(s.WarehouseCode)) IN (
				'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
				'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
				'1-0-33','101','1-014','1-0-49','1-0-36'
			  )
			GROUP BY s.ItemCode
		),
		stock_base AS (
			SELECT
				s.ItemCode AS ItemCode,
				SUM(s.In_Qty1 - s.Out_Qty1) AS InventoryQty1
			FROM trStock s
			INNER JOIN req_codes rc
				ON rc.ProductCode = s.ItemCode
			WHERE s.ItemTypeCode = 1
			  AND LEN(s.ItemCode) = 13
			GROUP BY s.ItemCode
		),
		pick_base AS (
			SELECT
				p.ItemCode AS ItemCode,
				SUM(p.Qty1) AS PickingQty1
			FROM PickingStates p
			INNER JOIN req_codes rc
				ON rc.ProductCode = p.ItemCode
			WHERE p.ItemTypeCode = 1
			  AND LEN(p.ItemCode) = 13
			GROUP BY p.ItemCode
		),
		reserve_base AS (
			SELECT
				r.ItemCode AS ItemCode,
				SUM(r.Qty1) AS ReserveQty1
			FROM ReserveStates r
			INNER JOIN req_codes rc
				ON rc.ProductCode = r.ItemCode
			WHERE r.ItemTypeCode = 1
			  AND LEN(r.ItemCode) = 13
			GROUP BY r.ItemCode
		),
		disp_base AS (
			SELECT
				d.ItemCode AS ItemCode,
				SUM(d.Qty1) AS DispOrderQty1
			FROM DispOrderStates d
			INNER JOIN req_codes rc
				ON rc.ProductCode = d.ItemCode
			WHERE d.ItemTypeCode = 1
			  AND LEN(d.ItemCode) = 13
			GROUP BY d.ItemCode
		)
		SELECT
			rc.ProductCode,
			COALESCE(lp.CostPrice, 0) AS CostPrice,
			COALESCE(bp.BasePriceUsd, 0) AS BasePriceUsd,
			COALESCE(bp.BasePriceTry, 0) AS BasePriceTry,
			CAST(ROUND(
				ISNULL(sb.InventoryQty1, 0)
				- ISNULL(pb.PickingQty1, 0)
				- ISNULL(rb.ReserveQty1, 0)
				- ISNULL(db.DispOrderQty1, 0)
			, 2) AS DECIMAL(18, 2)) AS StockQty,
			COALESCE(se.StockEntryDate, '') AS StockEntryDate,
			'' AS LastCostingDate,
			COALESCE(lp.LastPricingDate, '') AS LastPricingDate
		FROM req_codes rc
		LEFT JOIN latest_base_price lp
			ON lp.ItemCode = rc.ProductCode
		   AND lp.rn = 1
		LEFT JOIN base_prices bp
			ON bp.ItemCode = rc.ProductCode
		LEFT JOIN stock_entry_dates se
			ON se.ItemCode = rc.ProductCode
		LEFT JOIN stock_base sb
			ON sb.ItemCode = rc.ProductCode
		LEFT JOIN pick_base pb
			ON pb.ItemCode = rc.ProductCode
		LEFT JOIN reserve_base rb
			ON rb.ItemCode = rc.ProductCode
		LEFT JOIN disp_base db
			ON db.ItemCode = rc.ProductCode;
	`

	metricsRows, err := db.MssqlDB.QueryContext(ctx, metricsQuery, metricArgs...)
	if err != nil {
		return result, fmt.Errorf("metrics query failed: %w", err)
	}
	defer metricsRows.Close()

	type metrics struct {
		CostPrice       float64
		BasePriceUsd    float64
		BasePriceTry    float64
		StockQty        float64
		StockEntryDate  string
		LastCostingDate string
		LastPricingDate string
	}
	metricsByCode := make(map[string]metrics, len(out))
	for metricsRows.Next() {
		var (
			code string
			m    metrics
		)
		if err := metricsRows.Scan(
			&code,
			&m.CostPrice,
			&m.BasePriceUsd,
			&m.BasePriceTry,
			&m.StockQty,
			&m.StockEntryDate,
			&m.LastCostingDate,
			&m.LastPricingDate,
		); err != nil {
			return result, err
		}
		metricsByCode[strings.TrimSpace(code)] = m
	}
	if err := metricsRows.Err(); err != nil {
		return result, err
	}

	for i := range out {
		if m, ok := metricsByCode[strings.TrimSpace(out[i].ProductCode)]; ok {
			out[i].CostPrice = m.CostPrice
			out[i].BasePriceUsd = m.BasePriceUsd
			out[i].BasePriceTry = m.BasePriceTry
			out[i].StockQty = m.StockQty
			out[i].StockEntryDate = m.StockEntryDate
			out[i].LastCostingDate = m.LastCostingDate
			out[i].LastPricingDate = m.LastPricingDate
		}
	}

	// Stage 3: fetch costing date from UretimDB (separate MSSQL catalog).
	// Pricing DB may not contain spUrtOnMLMas; do not fail listing on costing query errors.
	if uretimDB := db.GetUretimDB(); uretimDB != nil {
		costingQuery := `
			WITH req_codes AS (
				SELECT DISTINCT LTRIM(RTRIM(v.ProductCode)) AS ProductCode
				FROM (VALUES ` + strings.Join(valueRows, ",") + `) v(ProductCode)
				WHERE LEN(LTRIM(RTRIM(v.ProductCode))) > 0
			)
			SELECT
				LTRIM(RTRIM(m.UrunKodu)) AS UrunKodu,
				CONVERT(VARCHAR(10), MAX(m.Tarihi), 23) AS LastCostingDate
			FROM dbo.spUrtOnMLMas m
			INNER JOIN req_codes rc
				ON rc.ProductCode = LTRIM(RTRIM(m.UrunKodu))
			GROUP BY LTRIM(RTRIM(m.UrunKodu));
		`
		costRows, err := uretimDB.QueryContext(ctx, costingQuery, metricArgs...)
		if err == nil {
			costingByCode := make(map[string]string, len(out))
			for costRows.Next() {
				var code, d string
				if err := costRows.Scan(&code, &d); err != nil {
					_ = costRows.Close()
					costRows = nil
					break
				}
				costingByCode[strings.TrimSpace(code)] = strings.TrimSpace(d)
			}
			if costRows != nil {
				_ = costRows.Close()
				for i := range out {
					if d, ok := costingByCode[strings.TrimSpace(out[i].ProductCode)]; ok && d != "" {
						out[i].LastCostingDate = d
					}
				}
			}
		}
	}

	// Stage 4: fetch latest tier prices (USD1..6, EUR1..6, TRY1..6) from PostgreSQL sdprc/mmitem.
	if pg := db.PgDB; pg != nil {
		type tierRow struct {
			Code string
			Grp  int
			Crn  string
			Prc  float64
		}
		tierSQL := `
			WITH ranked AS (
				SELECT
					mmitem.code AS code,
					sdprc.sdprcgrp_id AS grp,
					sdprc.crn AS crn,
					COALESCE(sdprc.prc, 0) AS prc,
					ROW_NUMBER() OVER (
						PARTITION BY mmitem.code, sdprc.crn, sdprc.sdprcgrp_id
						ORDER BY sdprc.zlins_dttm DESC
					) AS rn
				FROM sdprc
				JOIN mmitem ON mmitem.id = sdprc.mmitem_id
				WHERE mmitem.code = ANY($1)
				  AND sdprc.sdprcgrp_id BETWEEN 1 AND 6
				  AND sdprc.crn IN ('USD', 'EUR', 'TRY')
				  AND sdprc.prc IS NOT NULL
				  AND sdprc.prc > 0
			)
			SELECT code, grp, crn, prc
			FROM ranked
			WHERE rn = 1;
		`
		rows, err := pg.QueryContext(ctx, tierSQL, pq.Array(codes))
		if err == nil {
			defer rows.Close()
			type key struct {
				Code string
			}
			tiers := make(map[string]map[string]map[int]float64, len(out))
			for rows.Next() {
				var r tierRow
				if err := rows.Scan(&r.Code, &r.Grp, &r.Crn, &r.Prc); err != nil {
					break
				}
				r.Code = strings.TrimSpace(r.Code)
				r.Crn = strings.TrimSpace(strings.ToUpper(r.Crn))
				if r.Code == "" || r.Grp < 1 || r.Grp > 6 || r.Crn == "" {
					continue
				}
				if _, ok := tiers[r.Code]; !ok {
					tiers[r.Code] = map[string]map[int]float64{}
				}
				if _, ok := tiers[r.Code][r.Crn]; !ok {
					tiers[r.Code][r.Crn] = map[int]float64{}
				}
				tiers[r.Code][r.Crn][r.Grp] = r.Prc
			}

			for i := range out {
				code := strings.TrimSpace(out[i].ProductCode)
				m, ok := tiers[code]
				if !ok {
					continue
				}
				apply := func(crn string, grp int, v float64) {
					switch crn {
					case "USD":
						switch grp {
						case 1:
							out[i].USD1 = v
						case 2:
							out[i].USD2 = v
						case 3:
							out[i].USD3 = v
						case 4:
							out[i].USD4 = v
						case 5:
							out[i].USD5 = v
						case 6:
							out[i].USD6 = v
						}
					case "EUR":
						switch grp {
						case 1:
							out[i].EUR1 = v
						case 2:
							out[i].EUR2 = v
						case 3:
							out[i].EUR3 = v
						case 4:
							out[i].EUR4 = v
						case 5:
							out[i].EUR5 = v
						case 6:
							out[i].EUR6 = v
						}
					case "TRY":
						switch grp {
						case 1:
							out[i].TRY1 = v
						case 2:
							out[i].TRY2 = v
						case 3:
							out[i].TRY3 = v
						case 4:
							out[i].TRY4 = v
						case 5:
							out[i].TRY5 = v
						case 6:
							out[i].TRY6 = v
						}
					}
				}
				for crn, byGrp := range m {
					for grp, v := range byGrp {
						apply(crn, grp, v)
					}
				}
			}
		}
	}

	// Stage 5: brand group (classification) from Postgres mk_brandgrpmatch.
	// Show classification result in BrandGroupSec field (read-only in UI).
	if pg := db.PgDB; pg != nil {
		brandCodes := make([]string, 0, len(out))
		seen := make(map[string]struct{}, len(out))
		for _, it := range out {
			code := strings.TrimSpace(it.BrandCode)
			if code == "" {
				continue
			}
			if _, ok := seen[code]; ok {
				continue
			}
			seen[code] = struct{}{}
			brandCodes = append(brandCodes, code)
		}
		if len(brandCodes) > 0 {
			rows, err := pg.QueryContext(ctx, `
SELECT
  m.brand_code,
  COALESCE(g.title, '') AS grp_title
FROM mk_brandgrpmatch m
JOIN mk_brandgrp g ON g.id = m.grp_id
WHERE m.brand_code = ANY($1)
`, pq.Array(brandCodes))
			if err == nil {
				defer rows.Close()
				grpByBrand := make(map[string]string, len(brandCodes))
				for rows.Next() {
					var code, title string
					if err := rows.Scan(&code, &title); err != nil {
						break
					}
					grpByBrand[strings.TrimSpace(code)] = strings.TrimSpace(title)
				}
				for i := range out {
					if title, ok := grpByBrand[strings.TrimSpace(out[i].BrandCode)]; ok {
						out[i].BrandGroupSec = title
					} else {
						out[i].BrandGroupSec = ""
					}
				}
			}
		}
	}

	result.Rows = out
	return result, nil
}

func isTransientMSSQLNetworkError(err error) bool {
	if err == nil {
		return false
	}
	e := strings.ToLower(err.Error())
	return strings.Contains(e, "i/o timeout") ||
		strings.Contains(e, "timeout") ||
		strings.Contains(e, "wsarecv") ||
		strings.Contains(e, "connection attempt failed") ||
		strings.Contains(e, "no connection could be made") ||
		strings.Contains(e, "broken pipe") ||
		strings.Contains(e, "connection reset")
}
