package queries

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

	"bssapp-backend/db"
)

func GetStatementAgingBalanceList(ctx context.Context, params models.CustomerBalanceListParams) ([]models.CustomerBalanceListRow, error) {
	selectedDate := strings.TrimSpace(params.SelectedDate)
	if selectedDate == "" {
		selectedDate = time.Now().Format("2006-01-02")
	}

	lines, err := loadAgingBalanceLines(ctx, strings.TrimSpace(params.CariSearch))
	if err != nil {
		return nil, err
	}

	metaMap, err := loadCariMetaMap(ctx, lines)
	if err != nil {
		log.Printf("statement_aging_balance: cari meta query failed, fallback without meta: %v", err)
		metaMap = map[string]cariMeta{}
	}

	masterMetaMap, err := loadMasterCariMetaMap(ctx, lines)
	if err != nil {
		log.Printf("statement_aging_balance: master cari meta query failed, fallback without master meta: %v", err)
		masterMetaMap = map[string]masterCariMeta{}
	}

	companyMap, err := loadCompanyMap(ctx)
	if err != nil {
		return nil, err
	}

	glMap, err := loadGLAccountMap(ctx, lines)
	if err != nil {
		return nil, err
	}

	rateMap, err := loadNearestTryRates(ctx)
	if err != nil {
		return nil, err
	}
	usdTry := rateMap["USD"]
	if usdTry <= 0 {
		usdTry = 1
	}

	filters := buildFilters(params)
	agg := make(map[string]*models.CustomerBalanceListRow, len(lines))
	weightMap := make(map[string]float64, len(lines))
	vadeSumMap := make(map[string]float64, len(lines))
	vadeBelgeSumMap := make(map[string]float64, len(lines))

	for _, ln := range lines {
		cari := strings.TrimSpace(ln.CariKodu)
		if cari == "" {
			continue
		}

		curr := strings.ToUpper(strings.TrimSpace(ln.CariDoviz))
		if curr == "" {
			curr = "TRY"
		}

		meta := metaMap[metaKey(ln.CurrAccTypeCode, cari)]
		meta.MuhasebeKodu = glMap[glKey(ln.CurrAccTypeCode, cari, ln.SirketKodu)]
		meta.SirketDetay = companyMap[ln.SirketKodu]
		master := deriveMasterCari(cari)
		mm := masterMetaMap[master]

		if strings.TrimSpace(mm.Kanal1) != "" {
			meta.Kanal1 = mm.Kanal1
		}
		if strings.TrimSpace(mm.Piyasa) != "" {
			meta.Piyasa = mm.Piyasa
		}
		if strings.TrimSpace(mm.Temsilci) != "" {
			meta.Temsilci = mm.Temsilci
		}
		if strings.TrimSpace(mm.Ulke) != "" {
			meta.Ulke = mm.Ulke
		}
		if strings.TrimSpace(mm.Il) != "" {
			meta.Il = mm.Il
		}
		if strings.TrimSpace(mm.Ilce) != "" {
			meta.Ilce = mm.Ilce
		}
		if strings.TrimSpace(mm.RiskDurumu) != "" {
			meta.RiskDurumu = mm.RiskDurumu
		}

		if !filters.matchLine(ln.PislemTipi, meta) {
			continue
		}

		key := strconv.Itoa(ln.CurrAccTypeCode) + "|" + cari + "|" + curr + "|" + strconv.Itoa(ln.SirketKodu)
		row, ok := agg[key]
		if !ok {
			row = &models.CustomerBalanceListRow{
				CariIlkGrup:  meta.Kanal1,
				Piyasa:       meta.Piyasa,
				Temsilci:     meta.Temsilci,
				Sirket:       strconv.Itoa(ln.SirketKodu),
				AnaCariKodu:  master,
				AnaCariAdi:   firstNonEmpty(mm.CariDetay, meta.CariDetay),
				CariKodu:     cari,
				CariDetay:    meta.CariDetay,
				CariTip:      meta.CariTip,
				Kanal1:       meta.Kanal1,
				Ozellik03:    meta.RiskDurumu,
				Ozellik05:    meta.Ulke,
				Ozellik06:    meta.Il,
				Ozellik07:    meta.Ilce,
				Il:           meta.Il,
				Ilce:         meta.Ilce,
				MuhasebeKodu: meta.MuhasebeKodu,
				TC:           meta.TC,
				RiskDurumu:   meta.RiskDurumu,
				SirketDetay:  meta.SirketDetay,
				CariDoviz:    curr,
			}
			agg[key] = row
		}

		usd := toUSD(ln.Bakiye, curr, usdTry, rateMap)
		tl := toTRY(ln.Bakiye, curr, rateMap)
		add12, add13 := resolveBalanceBuckets(ln)
		if add12 {
			row.Bakiye12 += ln.Bakiye
			row.TLBakiye12 += tl
			row.USDBakiye12 += usd
		}
		if add13 {
			row.Bakiye13 += ln.Bakiye
			row.TLBakiye13 += tl
			row.USDBakiye13 += usd
		}

		w := math.Abs(ln.Bakiye)
		if w > 0 {
			weightMap[key] += w
			vadeSumMap[key] += (ln.VadeGun * w)
			vadeBelgeSumMap[key] += (ln.VadeBelgeGun * w)
		}
	}

	out := make([]models.CustomerBalanceListRow, 0, len(agg))
	for k, v := range agg {
		base := weightMap[k]
		if base > 0 {
			v.VadeGun = vadeSumMap[k] / base
			v.VadeBelgeGun = vadeBelgeSumMap[k] / base
		}
		out = append(out, *v)
	}

	sort.Slice(out, func(i, j int) bool {
		if out[i].AnaCariKodu == out[j].AnaCariKodu {
			if out[i].CariKodu == out[j].CariKodu {
				return out[i].CariDoviz < out[j].CariDoviz
			}
			return out[i].CariKodu < out[j].CariKodu
		}
		return out[i].AnaCariKodu < out[j].AnaCariKodu
	})

	_ = selectedDate
	return out, nil
}

func loadAgingBalanceLines(ctx context.Context, cariSearch string) ([]mkCariBakiyeLine, error) {
	piyasaScope, err := buildPiyasaExistsForCariCode(ctx, "LTRIM(RTRIM(CariKodu))")
	if err != nil {
		return nil, err
	}

	queryTemplate := `
	SELECT
		CurrAccTypeCode,
		CariKodu = LTRIM(RTRIM(CariKodu)),
		CariDoviz = LTRIM(RTRIM(CariDoviz)),
		SirketKodu,
		PislemTipi,
		%s
		YerelBakiye = CAST(0 AS DECIMAL(18,2)),
		Bakiye,
		Vade_Gun,
		Vade_BelgeTarihi_Gun
	FROM dbo.CARI_BAKIYE_GUN_CACHE
	WHERE (@CariSearch = '' OR LTRIM(RTRIM(CariKodu)) LIKE '%%' + @CariSearch + '%%')
	  AND %s
	ORDER BY CariKodu, CariDoviz, PislemTipi
	`

	selectParasalCandidates := make([]string, 0, 7)
	if expr := strings.TrimSpace(resolveParasalIslemSelectExpr(ctx, "SELECT * FROM dbo.CARI_BAKIYE_GUN_CACHE")); expr != "" {
		selectParasalCandidates = append(selectParasalCandidates, expr)
	}
	selectParasalCandidates = append(selectParasalCandidates,
		"CAST(ATAtt01 AS varchar(16)) AS ParasalIslemTipi,",
		"CAST(ParasalIslemTipi AS varchar(16)) AS ParasalIslemTipi,",
		"CAST(ParislemTipi AS varchar(16)) AS ParasalIslemTipi,",
		"CAST(ParIslemTipi AS varchar(16)) AS ParasalIslemTipi,",
		"CAST('' AS varchar(16)) AS ParasalIslemTipi,",
	)

	var rows *sql.Rows
	for i, sel := range selectParasalCandidates {
		query := fmt.Sprintf(queryTemplate, sel, piyasaScope)
		rows, err = db.MssqlDB.QueryContext(ctx, query, sql.Named("CariSearch", strings.TrimSpace(cariSearch)))
		if err == nil {
			break
		}
		if i < len(selectParasalCandidates)-1 && isInvalidColumnError(err) {
			continue
		}
		return nil, fmt.Errorf("CARI_BAKIYE_GUN_CACHE query error: %w", err)
	}
	defer rows.Close()

	out := make([]mkCariBakiyeLine, 0, 4096)
	for rows.Next() {
		var r mkCariBakiyeLine
		if err := rows.Scan(
			&r.CurrAccTypeCode,
			&r.CariKodu,
			&r.CariDoviz,
			&r.SirketKodu,
			&r.PislemTipi,
			&r.ParasalIslemTipi,
			&r.YerelBakiye,
			&r.Bakiye,
			&r.VadeGun,
			&r.VadeBelgeGun,
		); err != nil {
			return nil, err
		}
		out = append(out, r)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return out, nil
}

func toTRY(amount float64, currency string, rateMap map[string]float64) float64 {
	switch currency {
	case "TRY":
		return amount
	case "":
		return amount
	default:
		currTry := rateMap[currency]
		if currTry <= 0 {
			return 0
		}
		return amount * currTry
	}
}
