package queries

import (
	"context"
	"crypto/md5"
	"database/sql"
	"encoding/hex"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/lib/pq"
)

type PricingParameterSyncResult struct {
	Total       int `json:"total"`
	Upserted    int `json:"upserted"`
	Deactivated int `json:"deactivated"`
}

type pricingParameterRow struct {
	AskiliYan     string
	Kategori      string
	UrunIlkGrubu  string
	UrunAnaGrubu  string
	UrunAltGrubu  string
	Icerik        string
	Marka         string
	BrandCode     string
	BrandGroupSec string
}

func PricingParameterRowForImport(
	askiliYan, kategori, urunIlkGrubu, urunAnaGrubu, urunAltGrubu,
	icerik, marka, brandCode, brandGroupSec string,
) pricingParameterRow {
	return pricingParameterRow{
		AskiliYan:     askiliYan,
		Kategori:      kategori,
		UrunIlkGrubu:  urunIlkGrubu,
		UrunAnaGrubu:  urunAnaGrubu,
		UrunAltGrubu:  urunAltGrubu,
		Icerik:        icerik,
		Marka:         marka,
		BrandCode:     brandCode,
		BrandGroupSec: brandGroupSec,
	}
}

type PricingParameterRuleRow struct {
	PricingParameterID int64           `json:"pricing_parameter_id"`
	ScopeKey           string          `json:"scope_key"`
	AskiliYan          string          `json:"askili_yan"`
	Kategori           string          `json:"kategori"`
	UrunIlkGrubu       string          `json:"urun_ilk_grubu"`
	UrunAnaGrubu       string          `json:"urun_ana_grubu"`
	UrunAltGrubu       string          `json:"urun_alt_grubu"`
	Icerik             string          `json:"icerik"`
	Marka              string          `json:"marka"`
	BrandCode          string          `json:"brand_code"`
	BrandGroupSec      string          `json:"brand_group"`
	HasRule            bool            `json:"has_rule"`
	Rule               *PricingRuleRow `json:"rule"`
}

// EnsurePricingParameterTables keeps the MSSQL-derived cascade cache close to
// the pricing rules. Rows are retained when they disappear from MSSQL and
// marked inactive so historical rule scopes remain understandable.
func EnsurePricingParameterTables(pg *sql.DB) error {
	stmts := []string{
		`
CREATE TABLE IF NOT EXISTS mk_urunpricingprmtr (
  id                BIGSERIAL PRIMARY KEY,
  askili_yan        TEXT NOT NULL DEFAULT '',
  kategori          TEXT NOT NULL DEFAULT '',
  urun_ilk_grubu    TEXT NOT NULL DEFAULT '',
  urun_ana_grubu    TEXT NOT NULL DEFAULT '',
  urun_alt_grubu    TEXT NOT NULL DEFAULT '',
  icerik            TEXT NOT NULL DEFAULT '',
  marka             TEXT NOT NULL DEFAULT '',
  brand_code        TEXT NOT NULL DEFAULT '',
  brand_group_sec   TEXT NOT NULL DEFAULT '',
  is_active         BOOLEAN NOT NULL DEFAULT TRUE,
  first_seen_at     TIMESTAMPTZ NOT NULL DEFAULT now(),
  last_seen_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
  scope_key          TEXT GENERATED ALWAYS AS (
    md5(askili_yan || chr(31) || kategori || chr(31) || urun_ilk_grubu ||
      chr(31) || urun_ana_grubu || chr(31) || urun_alt_grubu || chr(31) ||
      icerik || chr(31) || marka || chr(31) || brand_code || chr(31) ||
      brand_group_sec)
  ) STORED
)`,
		`DROP INDEX IF EXISTS ux_mk_urunpricingprmtr_scope`,
		`
DO $$
BEGIN
  IF EXISTS (
    SELECT 1
    FROM information_schema.columns
    WHERE table_schema=current_schema()
      AND table_name='mk_urunpricingprmtr'
      AND column_name='karisim'
  ) THEN
    DROP INDEX IF EXISTS ux_mk_urunpricingprmtr_active_scope;
    DROP INDEX IF EXISTS ix_mk_urunpricingprmtr_scope_history;
    ALTER TABLE mk_urunpricingprmtr DROP COLUMN IF EXISTS scope_key;
    ALTER TABLE mk_urunpricingprmtr DROP COLUMN karisim;
  END IF;
END $$`,
		`
ALTER TABLE mk_urunpricingprmtr
ADD COLUMN IF NOT EXISTS scope_key TEXT GENERATED ALWAYS AS (
  md5(askili_yan || chr(31) || kategori || chr(31) || urun_ilk_grubu ||
    chr(31) || urun_ana_grubu || chr(31) || urun_alt_grubu || chr(31) ||
    icerik || chr(31) || marka || chr(31) || brand_code || chr(31) ||
    brand_group_sec)
) STORED`,
		`
WITH ranked AS (
  SELECT
    id,
    ROW_NUMBER() OVER (PARTITION BY scope_key ORDER BY last_seen_at DESC, id DESC) AS rn
  FROM mk_urunpricingprmtr
  WHERE is_active=TRUE
)
UPDATE mk_urunpricingprmtr p
SET is_active=FALSE
FROM ranked r
WHERE p.id=r.id
  AND r.rn > 1`,
		`CREATE UNIQUE INDEX IF NOT EXISTS ux_mk_urunpricingprmtr_active_scope ON mk_urunpricingprmtr (scope_key) WHERE is_active = TRUE`,
		`CREATE INDEX IF NOT EXISTS ix_mk_urunpricingprmtr_scope_history ON mk_urunpricingprmtr (scope_key, last_seen_at DESC, id DESC)`,
		`CREATE INDEX IF NOT EXISTS ix_mk_urunpricingprmtr_active ON mk_urunpricingprmtr (is_active)`,
		`CREATE INDEX IF NOT EXISTS ix_mk_urunpricingprmtr_ilk_ana ON mk_urunpricingprmtr (urun_ilk_grubu, urun_ana_grubu) WHERE is_active = TRUE`,
		`CREATE INDEX IF NOT EXISTS ix_mk_urunpricingprmtr_brand ON mk_urunpricingprmtr (brand_code, brand_group_sec) WHERE is_active = TRUE`,
		`ALTER TABLE mk_pricing_rule ADD COLUMN IF NOT EXISTS pricing_parameter_id BIGINT REFERENCES mk_urunpricingprmtr(id) ON DELETE SET NULL`,
		`DROP INDEX IF EXISTS ux_mk_pricing_rule_parameter`,
		`CREATE INDEX IF NOT EXISTS ix_mk_pricing_rule_parameter_latest ON mk_pricing_rule (pricing_parameter_id, created_at DESC, updated_at DESC) WHERE pricing_parameter_id IS NOT NULL`,
	}
	for _, stmt := range stmts {
		if _, err := pg.Exec(stmt); err != nil {
			return err
		}
	}
	return nil
}

func FillPricingRuleScopeFromParameter(ctx context.Context, tx *sql.Tx, item *PricingRuleSaveItem) error {
	if item == nil || item.PricingParameterID <= 0 {
		return nil
	}
	var p pricingParameterRow
	if err := tx.QueryRowContext(ctx, `
SELECT
  askili_yan, kategori, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu,
  icerik, marka, brand_code, brand_group_sec
FROM mk_urunpricingprmtr
WHERE id=$1 AND is_active=TRUE
`, item.PricingParameterID).Scan(
		&p.AskiliYan,
		&p.Kategori,
		&p.UrunIlkGrubu,
		&p.UrunAnaGrubu,
		&p.UrunAltGrubu,
		&p.Icerik,
		&p.Marka,
		&p.BrandCode,
		&p.BrandGroupSec,
	); err != nil {
		return err
	}
	item.AskiliYan = pricingParameterScopeValue(p.AskiliYan)
	item.Kategori = pricingParameterScopeValue(p.Kategori)
	item.UrunIlkGrubu = pricingParameterScopeValue(p.UrunIlkGrubu)
	item.UrunAnaGrubu = pricingParameterScopeValue(p.UrunAnaGrubu)
	item.UrunAltGrubu = pricingParameterScopeValue(p.UrunAltGrubu)
	item.Icerik = pricingParameterScopeValue(p.Icerik)
	item.Karisim = nil
	item.Marka = pricingParameterScopeValue(p.Marka)
	item.BrandCode = pricingParameterScopeValue(p.BrandCode)
	item.BrandGroupSec = pricingParameterScopeValue(p.BrandGroupSec)
	return nil
}

func VersionPricingParameterForRule(ctx context.Context, tx *sql.Tx, pricingParameterID int64) (int64, error) {
	if pricingParameterID <= 0 {
		return 0, nil
	}

	var p pricingParameterRow
	var scopeKey string
	if err := tx.QueryRowContext(ctx, `
SELECT
  askili_yan, kategori, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu,
  icerik, marka, brand_code, brand_group_sec, scope_key
FROM mk_urunpricingprmtr
WHERE id=$1
`, pricingParameterID).Scan(
		&p.AskiliYan,
		&p.Kategori,
		&p.UrunIlkGrubu,
		&p.UrunAnaGrubu,
		&p.UrunAltGrubu,
		&p.Icerik,
		&p.Marka,
		&p.BrandCode,
		&p.BrandGroupSec,
		&scopeKey,
	); err != nil {
		return 0, err
	}

	if _, err := tx.ExecContext(ctx, `
UPDATE mk_urunpricingprmtr
SET is_active=FALSE,
    last_seen_at=now()
WHERE scope_key=$1
  AND is_active=TRUE
`, scopeKey); err != nil {
		return 0, err
	}

	var newID int64
	if err := tx.QueryRowContext(ctx, `
INSERT INTO mk_urunpricingprmtr (
  askili_yan, kategori, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu,
  icerik, marka, brand_code, brand_group_sec,
  is_active, first_seen_at, last_seen_at
)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,TRUE,now(),now())
RETURNING id
`,
		p.AskiliYan,
		p.Kategori,
		p.UrunIlkGrubu,
		p.UrunAnaGrubu,
		p.UrunAltGrubu,
		p.Icerik,
		p.Marka,
		p.BrandCode,
		p.BrandGroupSec,
	).Scan(&newID); err != nil {
		return 0, err
	}
	return newID, nil
}

func pricingParameterScopeValue(value string) []string {
	value = strings.TrimSpace(value)
	if value == "" {
		return nil
	}
	return []string{value}
}

func EnsureActivePricingParameterByScope(ctx context.Context, tx *sql.Tx, row pricingParameterRow) (int64, bool, error) {
	row.AskiliYan = strings.TrimSpace(row.AskiliYan)
	row.Kategori = strings.TrimSpace(row.Kategori)
	row.UrunIlkGrubu = strings.TrimSpace(row.UrunIlkGrubu)
	row.UrunAnaGrubu = strings.TrimSpace(row.UrunAnaGrubu)
	row.UrunAltGrubu = strings.TrimSpace(row.UrunAltGrubu)
	row.Icerik = strings.TrimSpace(row.Icerik)
	row.Marka = strings.TrimSpace(row.Marka)
	row.BrandCode = strings.TrimSpace(row.BrandCode)
	row.BrandGroupSec = strings.TrimSpace(row.BrandGroupSec)

	var existingID int64
	var isActive bool
	err := tx.QueryRowContext(ctx, `
SELECT id, is_active
FROM mk_urunpricingprmtr
WHERE askili_yan=$1
  AND kategori=$2
  AND urun_ilk_grubu=$3
  AND urun_ana_grubu=$4
  AND urun_alt_grubu=$5
  AND icerik=$6
  AND marka=$7
  AND brand_code=$8
  AND brand_group_sec=$9
ORDER BY is_active DESC, last_seen_at DESC, id DESC
LIMIT 1
`,
		row.AskiliYan,
		row.Kategori,
		row.UrunIlkGrubu,
		row.UrunAnaGrubu,
		row.UrunAltGrubu,
		row.Icerik,
		row.Marka,
		row.BrandCode,
		row.BrandGroupSec,
	).Scan(&existingID, &isActive)
	if err == nil {
		if isActive {
			return existingID, false, nil
		}
	} else if err != sql.ErrNoRows {
		return 0, false, err
	}

	var newID int64
	if err := tx.QueryRowContext(ctx, `
INSERT INTO mk_urunpricingprmtr (
  askili_yan, kategori, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu,
  icerik, marka, brand_code, brand_group_sec,
  is_active, first_seen_at, last_seen_at
)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,TRUE,now(),now())
RETURNING id
`,
		row.AskiliYan,
		row.Kategori,
		row.UrunIlkGrubu,
		row.UrunAnaGrubu,
		row.UrunAltGrubu,
		row.Icerik,
		row.Marka,
		row.BrandCode,
		row.BrandGroupSec,
	).Scan(&newID); err != nil {
		return 0, false, err
	}
	return newID, true, nil
}

func FindActivePricingParameterByScope(ctx context.Context, tx *sql.Tx, row pricingParameterRow) (int64, error) {
	row.AskiliYan = strings.TrimSpace(row.AskiliYan)
	row.Kategori = strings.TrimSpace(row.Kategori)
	row.UrunIlkGrubu = strings.TrimSpace(row.UrunIlkGrubu)
	row.UrunAnaGrubu = strings.TrimSpace(row.UrunAnaGrubu)
	row.UrunAltGrubu = strings.TrimSpace(row.UrunAltGrubu)
	row.Icerik = strings.TrimSpace(row.Icerik)
	row.Marka = strings.TrimSpace(row.Marka)
	row.BrandCode = strings.TrimSpace(row.BrandCode)
	row.BrandGroupSec = strings.TrimSpace(row.BrandGroupSec)

	var id int64
	err := tx.QueryRowContext(ctx, `
SELECT id
FROM mk_urunpricingprmtr
WHERE askili_yan=$1
  AND kategori=$2
  AND urun_ilk_grubu=$3
  AND urun_ana_grubu=$4
  AND urun_alt_grubu=$5
  AND icerik=$6
  AND marka=$7
  AND brand_code=$8
  AND brand_group_sec=$9
  AND is_active=TRUE
ORDER BY last_seen_at DESC, id DESC
LIMIT 1
`,
		row.AskiliYan,
		row.Kategori,
		row.UrunIlkGrubu,
		row.UrunAnaGrubu,
		row.UrunAltGrubu,
		row.Icerik,
		row.Marka,
		row.BrandCode,
		row.BrandGroupSec,
	).Scan(&id)
	if err != nil {
		return 0, err
	}
	return id, nil
}

func SyncPricingParametersFromMSSQL(ctx context.Context, mssql *sql.DB, pg *sql.DB) (PricingParameterSyncResult, error) {
	out := PricingParameterSyncResult{}
	startedAt := time.Now()
	if mssql == nil || pg == nil {
		return out, sql.ErrConnDone
	}
	if err := EnsurePricingRuleTables(pg); err != nil {
		return out, err
	}
	if err := EnsurePricingParameterTables(pg); err != nil {
		return out, err
	}
	if err := EnsureBrandClassificationTables(pg); err != nil {
		return out, err
	}

	rows, err := mssql.QueryContext(ctx, `
SELECT DISTINCT
  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(ProductAtt10Desc)), '') AS Marka,
  COALESCE(LTRIM(RTRIM(ProductAtt10)), '') AS BrandCode
FROM ProductFilterWithDescription('TR')
WHERE ProductAtt42 IN ('SERI', 'AKSESUAR')
  AND IsBlocked = 0
  AND LEN(LTRIM(RTRIM(ProductCode))) = 13;
`)
	if err != nil {
		return out, err
	}
	defer rows.Close()

	src := make([]pricingParameterRow, 0, 4096)
	for rows.Next() {
		var item pricingParameterRow
		if err := rows.Scan(
			&item.AskiliYan,
			&item.Kategori,
			&item.UrunIlkGrubu,
			&item.UrunAnaGrubu,
			&item.UrunAltGrubu,
			&item.Icerik,
			&item.Marka,
			&item.BrandCode,
		); err != nil {
			return out, err
		}
		item = trimPricingParameterRow(item)
		src = append(src, item)
	}
	if err := rows.Err(); err != nil {
		return out, err
	}
	out.Total = len(src)
	log.Printf("Pricing parameter sync source loaded: rows=%d duration=%s", out.Total, time.Since(startedAt))

	groupByBrand, err := pricingParameterBrandGroups(ctx, pg)
	if err != nil {
		return out, err
	}

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

	// Serialize writes touching mk_urunpricingprmtr/mk_pricing_rule/mk_pricex/mk_priceroll
	// to avoid deadlocks with bulk-save/import flows.
	if _, err := tx.ExecContext(ctx, `SELECT pg_advisory_xact_lock(1001, 1)`); err != nil {
		return out, err
	}

	if _, err := tx.ExecContext(ctx, `
CREATE TEMP TABLE tmp_urunpricingprmtr_sync (
  askili_yan      TEXT NOT NULL,
  kategori        TEXT NOT NULL,
  urun_ilk_grubu  TEXT NOT NULL,
  urun_ana_grubu  TEXT NOT NULL,
  urun_alt_grubu  TEXT NOT NULL,
  icerik          TEXT NOT NULL,
  marka           TEXT NOT NULL,
  brand_code      TEXT NOT NULL,
  brand_group_sec TEXT NOT NULL,
  scope_key       TEXT NOT NULL PRIMARY KEY
) ON COMMIT DROP
`); err != nil {
		return out, err
	}

	copyStmt, err := tx.PrepareContext(ctx, pq.CopyIn(
		"tmp_urunpricingprmtr_sync",
		"askili_yan",
		"kategori",
		"urun_ilk_grubu",
		"urun_ana_grubu",
		"urun_alt_grubu",
		"icerik",
		"marka",
		"brand_code",
		"brand_group_sec",
		"scope_key",
	))
	if err != nil {
		return out, err
	}

	seenScopeKeys := make(map[string]struct{}, len(src))
	for _, item := range src {
		item.BrandGroupSec = groupByBrand[item.BrandCode]
		scopeKey := pricingParameterScopeKey(item)
		if _, exists := seenScopeKeys[scopeKey]; exists {
			continue
		}
		seenScopeKeys[scopeKey] = struct{}{}
		if _, err := copyStmt.ExecContext(ctx,
			item.AskiliYan,
			item.Kategori,
			item.UrunIlkGrubu,
			item.UrunAnaGrubu,
			item.UrunAltGrubu,
			item.Icerik,
			item.Marka,
			item.BrandCode,
			item.BrandGroupSec,
			scopeKey,
		); err != nil {
			_ = copyStmt.Close()
			return out, err
		}
	}
	if _, err := copyStmt.ExecContext(ctx); err != nil {
		_ = copyStmt.Close()
		return out, err
	}
	if err := copyStmt.Close(); err != nil {
		return out, err
	}
	out.Upserted = len(seenScopeKeys)
	log.Printf("Pricing parameter sync copy loaded: rows=%d duration=%s", out.Upserted, time.Since(startedAt))

	res, err := tx.ExecContext(ctx, `
UPDATE mk_urunpricingprmtr p
SET is_active=FALSE
WHERE p.is_active=TRUE
  AND NOT EXISTS (
    SELECT 1
    FROM tmp_urunpricingprmtr_sync t
    WHERE t.scope_key=p.scope_key
  )
`)
	if err != nil {
		return out, err
	}
	if n, err := res.RowsAffected(); err == nil {
		out.Deactivated = int(n)
	}

	if _, err := tx.ExecContext(ctx, `
UPDATE mk_urunpricingprmtr p
SET last_seen_at=now()
FROM tmp_urunpricingprmtr_sync t
WHERE p.scope_key=t.scope_key
  AND p.is_active=TRUE
`); err != nil {
		return out, err
	}

	insertResult, err := tx.ExecContext(ctx, `
INSERT INTO mk_urunpricingprmtr (
  askili_yan, kategori, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu,
  icerik, marka, brand_code, brand_group_sec,
  is_active, first_seen_at, last_seen_at
)
SELECT
  askili_yan, kategori, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu,
  icerik, marka, brand_code, brand_group_sec,
  TRUE, now(), now()
FROM tmp_urunpricingprmtr_sync t
WHERE NOT EXISTS (
  SELECT 1
  FROM mk_urunpricingprmtr p
  WHERE p.scope_key=t.scope_key
    AND p.is_active=TRUE
)
`)
	if err != nil {
		return out, err
	}
	if n, err := insertResult.RowsAffected(); err == nil {
		out.Upserted = int(n)
	}

	if err := tx.Commit(); err != nil {
		return out, err
	}
	log.Printf("Pricing parameter sync committed: rows=%d duration=%s", out.Upserted, time.Since(startedAt))
	return out, nil
}

func pricingParameterScopeKey(item pricingParameterRow) string {
	parts := []string{
		item.AskiliYan,
		item.Kategori,
		item.UrunIlkGrubu,
		item.UrunAnaGrubu,
		item.UrunAltGrubu,
		item.Icerik,
		item.Marka,
		item.BrandCode,
		item.BrandGroupSec,
	}
	sum := md5.Sum([]byte(strings.Join(parts, string(rune(31)))))
	return hex.EncodeToString(sum[:])
}

func trimPricingParameterRow(item pricingParameterRow) pricingParameterRow {
	item.AskiliYan = strings.TrimSpace(item.AskiliYan)
	item.Kategori = strings.TrimSpace(item.Kategori)
	item.UrunIlkGrubu = strings.TrimSpace(item.UrunIlkGrubu)
	item.UrunAnaGrubu = strings.TrimSpace(item.UrunAnaGrubu)
	item.UrunAltGrubu = strings.TrimSpace(item.UrunAltGrubu)
	item.Icerik = strings.TrimSpace(item.Icerik)
	item.Marka = strings.TrimSpace(item.Marka)
	item.BrandCode = strings.TrimSpace(item.BrandCode)
	item.BrandGroupSec = strings.TrimSpace(item.BrandGroupSec)
	return item
}

func pricingParameterBrandGroups(ctx context.Context, pg *sql.DB) (map[string]string, error) {
	rows, err := pg.QueryContext(ctx, `
SELECT m.brand_code, g.title
FROM mk_brandgrpmatch m
JOIN mk_brandgrp g ON g.id = m.grp_id
`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	out := make(map[string]string, 1024)
	for rows.Next() {
		var code, group string
		if err := rows.Scan(&code, &group); err != nil {
			return nil, err
		}
		out[strings.TrimSpace(code)] = strings.TrimSpace(group)
	}
	return out, rows.Err()
}

func ListPricingParameterDistinctOptions(ctx context.Context, pg *sql.DB, field string, f PricingRuleOptionFilters, limit int) ([]string, error) {
	field = strings.TrimSpace(field)
	if limit <= 0 {
		limit = 500
	}
	fieldMap := map[string]string{
		"askili_yan":     "askili_yan",
		"kategori":       "kategori",
		"urun_ilk_grubu": "urun_ilk_grubu",
		"urun_ana_grubu": "urun_ana_grubu",
		"urun_alt_grubu": "urun_alt_grubu",
		"icerik":         "icerik",
		"marka":          "marka",
		"brand_code":     "brand_code",
		"brand_group":    "brand_group_sec",
	}
	target, ok := fieldMap[field]
	if !ok {
		return nil, fmt.Errorf("invalid field")
	}

	type filter struct {
		Field  string
		Values []string
	}
	filters := []filter{
		{"askili_yan", f.AskiliYan},
		{"kategori", f.Kategori},
		{"urun_ilk_grubu", f.UrunIlkGrubu},
		{"urun_ana_grubu", f.UrunAnaGrubu},
		{"urun_alt_grubu", f.UrunAltGrubu},
		{"icerik", f.Icerik},
		{"marka", f.Marka},
		{"brand_code", f.BrandCode},
		{"brand_group", f.BrandGroupSec},
	}

	args := make([]any, 0, len(filters)+1)
	where := []string{"is_active=TRUE", target + " <> ''"}
	for _, item := range filters {
		if item.Field == field {
			continue
		}
		values := normalizeTextList(item.Values)
		if len(values) == 0 {
			continue
		}
		args = append(args, pq.Array(values))
		where = append(where, fieldMap[item.Field]+fmt.Sprintf(" = ANY($%d::text[])", len(args)))
	}
	args = append(args, limit)

	rows, err := pg.QueryContext(ctx, `
SELECT DISTINCT `+target+`
FROM mk_urunpricingprmtr
WHERE `+strings.Join(where, " AND ")+`
ORDER BY `+target+`
LIMIT $`+fmt.Sprint(len(args))+`
`, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	out := make([]string, 0, limit)
	for rows.Next() {
		var value string
		if err := rows.Scan(&value); err != nil {
			return nil, err
		}
		value = strings.TrimSpace(value)
		if value != "" {
			out = append(out, value)
		}
	}
	return out, rows.Err()
}

func ListPricingParameterRules(ctx context.Context, pg *sql.DB, f PricingRuleOptionFilters) ([]PricingParameterRuleRow, error) {
	where, args := pricingParameterFilterSQL(f)

	rows, err := pg.QueryContext(ctx, `
SELECT
  p.id,
  p.scope_key,
  p.askili_yan,
  p.kategori,
  p.urun_ilk_grubu,
  p.urun_ana_grubu,
  p.urun_alt_grubu,
  p.icerik,
  p.marka,
  p.brand_code,
  p.brand_group_sec,
  COALESCE(r.id::text, ''),
  COALESCE(
    r.strategy_code,
    CASE
      WHEN UPPER(BTRIM(p.brand_group_sec)) IN ('CORE','PREMIUM','SARTORIAL') THEN UPPER(BTRIM(p.brand_group_sec))
      ELSE 'CORE'
    END
  ),
  COALESCE(r.anchor_mode, bg.anchor_mode, 'USD'),
  COALESCE(r.calc_enabled, TRUE),
  COALESCE(r.publish_postgres, TRUE),
  COALESCE(r.publish_nebim, TRUE),
  COALESCE(r.is_active, TRUE),

  COALESCE(tx.base_mult, 0)::float8,
  COALESCE(tx.m1, 0)::float8,
  COALESCE(tx.m2, 0)::float8,
  COALESCE(tx.m3, 0)::float8,
  COALESCE(tx.m4, 0)::float8,
  COALESCE(tx.m5, 0)::float8,
  COALESCE(tx.m6, 0)::float8,
  COALESCE(NULLIF(tr.wholesale_step, 0), tr.step, 0)::float8,
  COALESCE(NULLIF(tr.retail_step, 0), tr.step, 0)::float8,
  COALESCE(NULLIF(BTRIM(tr.retail_mode), ''), 'STEP'),

  COALESCE(ux.base_mult, 0)::float8,
  COALESCE(ux.m1, 0)::float8,
  COALESCE(ux.m2, 0)::float8,
  COALESCE(ux.m3, 0)::float8,
  COALESCE(ux.m4, 0)::float8,
  COALESCE(ux.m5, 0)::float8,
  COALESCE(ux.m6, 0)::float8,
  COALESCE(NULLIF(ur.wholesale_step, 0), ur.step, 0)::float8,
  COALESCE(NULLIF(ur.retail_step, 0), ur.step, 0)::float8,
  COALESCE(NULLIF(BTRIM(ur.retail_mode), ''), 'STEP'),

  COALESCE(ex.base_mult, 0)::float8,
  COALESCE(ex.m1, 0)::float8,
  COALESCE(ex.m2, 0)::float8,
  COALESCE(ex.m3, 0)::float8,
  COALESCE(ex.m4, 0)::float8,
  COALESCE(ex.m5, 0)::float8,
  COALESCE(ex.m6, 0)::float8,
  COALESCE(NULLIF(er.wholesale_step, 0), er.step, 0)::float8,
  COALESCE(NULLIF(er.retail_step, 0), er.step, 0)::float8,
  COALESCE(NULLIF(BTRIM(er.retail_mode), ''), 'STEP')
FROM mk_urunpricingprmtr p
LEFT JOIN LATERAL (
  SELECT latest_rule.*
  FROM mk_pricing_rule latest_rule
  WHERE latest_rule.pricing_parameter_id = p.id
  ORDER BY latest_rule.created_at DESC, latest_rule.updated_at DESC, latest_rule.id DESC
  LIMIT 1
) r ON TRUE
LEFT JOIN LATERAL (
  SELECT g.anchor_mode
  FROM mk_brandgrp g
  WHERE UPPER(BTRIM(g.code)) = UPPER(BTRIM(p.brand_group_sec))
     OR UPPER(BTRIM(g.title)) = UPPER(BTRIM(p.brand_group_sec))
  ORDER BY g.id
  LIMIT 1
) bg ON TRUE
LEFT JOIN mk_pricex tx ON tx.rule_id = r.id AND tx.currency='TRY'
LEFT JOIN mk_pricex ux ON ux.rule_id = r.id AND ux.currency='USD'
LEFT JOIN mk_pricex ex ON ex.rule_id = r.id AND ex.currency='EUR'
LEFT JOIN mk_priceroll tr ON tr.rule_id = r.id AND tr.currency='TRY'
LEFT JOIN mk_priceroll ur ON ur.rule_id = r.id AND ur.currency='USD'
LEFT JOIN mk_priceroll er ON er.rule_id = r.id AND er.currency='EUR'
WHERE `+strings.Join(where, " AND ")+`
ORDER BY
  p.urun_ilk_grubu,
  p.urun_ana_grubu,
  p.urun_alt_grubu,
  p.marka,
  p.brand_code,
  p.id
`, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	out := make([]PricingParameterRuleRow, 0, 1024)
	for rows.Next() {
		var item PricingParameterRuleRow
		rule := PricingRuleRow{}
		if err := rows.Scan(
			&item.PricingParameterID,
			&item.ScopeKey,
			&item.AskiliYan,
			&item.Kategori,
			&item.UrunIlkGrubu,
			&item.UrunAnaGrubu,
			&item.UrunAltGrubu,
			&item.Icerik,
			&item.Marka,
			&item.BrandCode,
			&item.BrandGroupSec,
			&rule.ID,
			&rule.StrategyCode,
			&rule.AnchorMode,
			&rule.CalcEnabled,
			&rule.PublishPostgres,
			&rule.PublishNebim,
			&rule.IsActive,
			&rule.TryBase, &rule.Try1, &rule.Try2, &rule.Try3, &rule.Try4, &rule.Try5, &rule.Try6, &rule.TryWholesaleStep, &rule.TryRetailStep, &rule.TryRetailMode,
			&rule.UsdBase, &rule.Usd1, &rule.Usd2, &rule.Usd3, &rule.Usd4, &rule.Usd5, &rule.Usd6, &rule.UsdWholesaleStep, &rule.UsdRetailStep, &rule.UsdRetailMode,
			&rule.EurBase, &rule.Eur1, &rule.Eur2, &rule.Eur3, &rule.Eur4, &rule.Eur5, &rule.Eur6, &rule.EurWholesaleStep, &rule.EurRetailStep, &rule.EurRetailMode,
		); err != nil {
			return nil, err
		}
		rule.TryRetailMode = normalizeRetailMode(rule.TryRetailMode)
		rule.UsdRetailMode = normalizeRetailMode(rule.UsdRetailMode)
		rule.EurRetailMode = normalizeRetailMode(rule.EurRetailMode)
		rule.PricingParameterID = item.PricingParameterID
		rule.AskiliYan = pricingParameterScopeValue(item.AskiliYan)
		rule.Kategori = pricingParameterScopeValue(item.Kategori)
		rule.UrunIlkGrubu = pricingParameterScopeValue(item.UrunIlkGrubu)
		rule.UrunAnaGrubu = pricingParameterScopeValue(item.UrunAnaGrubu)
		rule.UrunAltGrubu = pricingParameterScopeValue(item.UrunAltGrubu)
		rule.Icerik = pricingParameterScopeValue(item.Icerik)
		rule.Karisim = nil
		rule.Marka = pricingParameterScopeValue(item.Marka)
		rule.BrandCode = pricingParameterScopeValue(item.BrandCode)
		rule.BrandGroupSec = pricingParameterScopeValue(item.BrandGroupSec)
		item.HasRule = strings.TrimSpace(rule.ID) != ""
		item.Rule = &rule
		out = append(out, item)
	}
	return out, rows.Err()
}

func pricingParameterFilterSQL(f PricingRuleOptionFilters) ([]string, []any) {
	type filter struct {
		Column string
		Values []string
	}
	filters := []filter{
		{"p.askili_yan", f.AskiliYan},
		{"p.kategori", f.Kategori},
		{"p.urun_ilk_grubu", f.UrunIlkGrubu},
		{"p.urun_ana_grubu", f.UrunAnaGrubu},
		{"p.urun_alt_grubu", f.UrunAltGrubu},
		{"p.icerik", f.Icerik},
		{"p.marka", f.Marka},
		{"p.brand_code", f.BrandCode},
		{"p.brand_group_sec", f.BrandGroupSec},
	}
	where := []string{"p.is_active=TRUE"}
	args := make([]any, 0, len(filters))
	for _, item := range filters {
		values := normalizeTextList(item.Values)
		if len(values) == 0 {
			continue
		}
		args = append(args, pq.Array(values))
		where = append(where, item.Column+fmt.Sprintf(" = ANY($%d::text[])", len(args)))
	}
	return where, args
}
