package queries

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

func GetStatementAging(params models.StatementAgingParams) ([]map[string]interface{}, error) {
	accountCode := normalizeMasterAccountCode(params.AccountCode)
	if strings.TrimSpace(params.EndDate) == "" {
		return nil, fmt.Errorf("enddate is required")
	}

	useType2, useType3 := resolveUseTypes(params.Parislemler)
	rateMap, err := loadNearestTryRates(context.Background())
	if err != nil {
		return nil, err
	}
	usdTry := rateMap["USD"]
	if usdTry <= 0 {
		usdTry = 1
	}

	rows, err := db.MssqlDB.Query(`
		EXEC dbo.SP_FIFO_MATCH_FINAL
			@Cari8    = @Cari8,
			@SonTarih = @SonTarih,
			@UseType2 = @UseType2,
			@UseType3 = @UseType3;
	`,
		sql.Named("Cari8", accountCode),
		sql.Named("SonTarih", params.EndDate),
		sql.Named("UseType2", useType2),
		sql.Named("UseType3", useType3),
	)
	if err != nil {
		return nil, fmt.Errorf("SP_FIFO_MATCH_FINAL query error: %w", err)
	}
	defer rows.Close()

	columns, err := rows.Columns()
	if err != nil {
		return nil, fmt.Errorf("columns read error: %w", err)
	}

	result := make([]map[string]interface{}, 0, 2048)
	cari8Set := make(map[string]struct{})
	for rows.Next() {
		values := make([]interface{}, len(columns))
		scanArgs := make([]interface{}, len(columns))
		for i := range values {
			scanArgs[i] = &values[i]
		}

		if err := rows.Scan(scanArgs...); err != nil {
			return nil, fmt.Errorf("row scan error: %w", err)
		}

		row := make(map[string]interface{}, len(columns))
		for i, col := range columns {
			switch v := values[i].(type) {
			case nil:
				row[col] = nil
			case []byte:
				row[col] = string(v)
			case time.Time:
				row[col] = v.Format("2006-01-02")
			default:
				row[col] = v
			}
		}

		cari8 := strings.TrimSpace(asString(row["Cari8"]))
		if cari8 != "" {
			cari8Set[cari8] = struct{}{}
		}

		result = append(result, row)
	}

	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("rows error: %w", err)
	}

	cariDetailMap, err := loadAgingMasterCariDetailMap(context.Background(), cari8Set)
	if err != nil {
		return nil, err
	}

	for i := range result {
		row := result[i]
		cari8 := strings.TrimSpace(asString(row["Cari8"]))
		curr := strings.ToUpper(strings.TrimSpace(asString(row["DocCurrencyCode"])))
		if curr == "" {
			curr = "TRY"
		}

		tutar := asFloat64(row["EslesenTutar"])
		usdTutar := toUSD(tutar, curr, usdTry, rateMap)
		currTry := rateMap[curr]
		usdToCurr := 0.0
		if currTry > 0 && usdTry > 0 {
			usdToCurr = usdTry / currTry
		}

		row["CariDetay"] = cariDetailMap[cari8]
		row["UsdTutar"] = round2(usdTutar)
		row["CurrencyTryRate"] = round6(currTry)
		row["UsdTryRate"] = round6(usdTry)
		row["CurrencyUsdRate"] = round6(usdToCurr)
	}

	return result, nil
}

func resolveUseTypes(parislemler []string) (int, int) {
	if len(parislemler) == 0 {
		return 1, 0
	}

	useType2 := 0
	useType3 := 0

	for _, v := range parislemler {
		switch strings.TrimSpace(v) {
		case "2":
			useType2 = 1
		case "3":
			useType3 = 1
		}
	}

	if useType2 == 0 && useType3 == 0 {
		return 1, 0
	}

	return useType2, useType3
}

func loadAgingMasterCariDetailMap(ctx context.Context, cari8Set map[string]struct{}) (map[string]string, error) {
	if len(cari8Set) == 0 {
		return map[string]string{}, nil
	}

	query := fmt.Sprintf(`
WITH BaseCari AS (
	SELECT
		CurrAccCode,
		CurrAccTypeCode,
		MasterCari = LEFT(CurrAccCode, 8),
		rn = ROW_NUMBER() OVER (
			PARTITION BY LEFT(CurrAccCode, 8)
			ORDER BY CurrAccCode
		)
	FROM cdCurrAcc
	WHERE CurrAccTypeCode IN (1,3)
	  AND LEFT(CurrAccCode, 8) IN (%s)
)
SELECT
	b.MasterCari,
	CariDetay = ISNULL(d.CurrAccDescription, '')
FROM BaseCari b
LEFT JOIN cdCurrAccDesc d
	ON d.CurrAccTypeCode = b.CurrAccTypeCode
   AND d.CurrAccCode = b.CurrAccCode
   AND d.LangCode = 'TR'
WHERE b.rn = 1;
`, quotedInList(cari8Set))

	rows, err := db.MssqlDB.QueryContext(ctx, query)
	if err != nil {
		return nil, fmt.Errorf("aging cari detail query error: %w", err)
	}
	defer rows.Close()

	out := make(map[string]string, len(cari8Set))
	for rows.Next() {
		var cari8 string
		var detail sql.NullString
		if err := rows.Scan(&cari8, &detail); err != nil {
			return nil, err
		}
		out[strings.TrimSpace(cari8)] = strings.TrimSpace(detail.String)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return out, nil
}

func asString(v interface{}) string {
	switch x := v.(type) {
	case nil:
		return ""
	case string:
		return x
	case []byte:
		return string(x)
	default:
		return fmt.Sprint(x)
	}
}

func asFloat64(v interface{}) float64 {
	switch x := v.(type) {
	case nil:
		return 0
	case float64:
		return x
	case float32:
		return float64(x)
	case int64:
		return float64(x)
	case int32:
		return float64(x)
	case int:
		return float64(x)
	case string:
		return parseNumberString(x)
	case []byte:
		return parseNumberString(string(x))
	default:
		return parseNumberString(fmt.Sprint(x))
	}
}

func parseNumberString(s string) float64 {
	s = strings.TrimSpace(s)
	if s == "" {
		return 0
	}

	hasComma := strings.Contains(s, ",")
	hasDot := strings.Contains(s, ".")
	if hasComma && hasDot {
		if strings.LastIndex(s, ",") > strings.LastIndex(s, ".") {
			s = strings.ReplaceAll(s, ".", "")
			s = strings.Replace(s, ",", ".", 1)
		} else {
			s = strings.ReplaceAll(s, ",", "")
		}
	} else if hasComma {
		s = strings.ReplaceAll(s, ".", "")
		s = strings.Replace(s, ",", ".", 1)
	}

	n, err := strconv.ParseFloat(s, 64)
	if err != nil {
		return 0
	}
	return n
}

func round2(v float64) float64 {
	return math.Round(v*100) / 100
}

func round6(v float64) float64 {
	return math.Round(v*1_000_000) / 1_000_000
}
