package routes

import (
	"context"
	"database/sql"
	"fmt"
	"math"
	"sort"
	"strings"
	"time"

	"bssapp-backend/internal/mailer"
	"bssapp-backend/models"
	"bssapp-backend/queries"
)

type mailBucket struct {
	InputByCur map[string]float64
	USD        float64
	TRY        float64
	HasCMT     bool
	// Fabric-only helpers (for UI parity)
	MeterQty float64
	MeterUom string
	UnitIn   float64
	UnitCur  string
}

func formatDateTR2(t time.Time) string {
	if t.IsZero() {
		return "-"
	}
	// dd.MM.yyyy
	return t.Format("02.01.2006")
}

func formatDateTimeTR2(t time.Time) string {
	if t.IsZero() {
		return "-"
	}
	// dd.MM.yyyy HH:mm
	return t.Format("02.01.2006 15:04")
}

func formatAnyDateTimeTR2(s string) string {
	s = strings.TrimSpace(s)
	if s == "" {
		return "-"
	}
	// Common MSSQL string renderings (best-effort).
	layouts := []string{
		"2006-01-02 15:04:05.9999999",
		"2006-01-02 15:04:05.999999",
		"2006-01-02 15:04:05.999",
		"2006-01-02 15:04:05",
		time.RFC3339Nano,
		time.RFC3339,
		"2006-01-02T15:04:05",
		"2006-01-02",
	}
	for _, layout := range layouts {
		if t, err := time.Parse(layout, s); err == nil {
			// Date-only vs datetime
			if layout == "2006-01-02" {
				return formatDateTR2(t)
			}
			return formatDateTimeTR2(t)
		}
	}
	return s
}

func addInputAmount(b *mailBucket, cur string, amount float64) {
	if math.IsNaN(amount) || math.IsInf(amount, 0) || amount == 0 {
		return
	}
	if b.InputByCur == nil {
		b.InputByCur = map[string]float64{}
	}
	b.InputByCur[cur] = b.InputByCur[cur] + amount
}

func formatMoney2(v float64) string {
	if math.IsNaN(v) || math.IsInf(v, 0) {
		v = 0
	}
	// Keep 2 decimals with dot (mail clients).
	return fmt.Sprintf("%.2f", v)
}

func formatQty2(v float64) string {
	if math.IsNaN(v) || math.IsInf(v, 0) {
		v = 0
	}
	return fmt.Sprintf("%.2f", v)
}

func normalizePartFromMtBolumTitle(title string) string {
	v := strings.ToUpper(strings.TrimSpace(title))
	if v == "" {
		return ""
	}
	// Keep the part name dynamic (UI shows spUrtMTBolum.sAdi). Still normalize a few aliases.
	switch {
	case strings.Contains(v, "AKSESUAR") || strings.Contains(v, "AKS"):
		return "AKSESUAR"
	default:
		return v
	}
}

func summarizeInputByCurrency(b *mailBucket) (amountLabel string, curLabel string) {
	if b == nil || len(b.InputByCur) == 0 {
		return "-", "-"
	}
	curs := make([]string, 0, len(b.InputByCur))
	for c := range b.InputByCur {
		c = strings.ToUpper(strings.TrimSpace(c))
		if c == "" {
			continue
		}
		curs = append(curs, c)
	}
	sort.Strings(curs)
	if len(curs) == 0 {
		return "-", "-"
	}
	if len(curs) == 1 {
		c := curs[0]
		return formatMoney2(b.InputByCur[c]), c
	}
	sum := 0.0
	for _, c := range curs {
		sum += b.InputByCur[c]
	}
	return formatMoney2(sum), "MIX"
}

func formatMeterLabel(b *mailBucket) string {
	if b == nil || !(b.MeterQty > 0) {
		return "-"
	}
	u := strings.TrimSpace(b.MeterUom)
	if u == "" {
		u = "MT"
	}
	return fmt.Sprintf("%s %s", formatQty2(b.MeterQty), u)
}

func loadCostingRecipients(pg *sql.DB, firstGroupCode string) ([]string, error) {
	rows, err := pg.Query(`
SELECT DISTINCT TRIM(m.email) AS email
FROM mk_costing_first_group_mail f
JOIN mk_mail m
  ON m.id = f.mail_id
WHERE m.is_active = true
  AND COALESCE(TRIM(m.email), '') <> ''
  AND UPPER(TRIM(f.urun_ilk_grubu)) = UPPER(TRIM($1))
ORDER BY email
`, strings.TrimSpace(firstGroupCode))
	if err != nil {
		return nil, err
	}
	defer rows.Close()

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

func sendCostingSummaryMail(
	ctx context.Context,
	pg *sql.DB,
	mssql *sql.DB,
	uretim *sql.DB,
	ml *mailer.GraphMailer,
	req models.ProductionProductCostingOnMLSaveRequest,
	nOnMLNo int,
	isUpdate bool,
	usdRate float64,
	eurRate float64,
	gbpRate float64,
	totalUSD float64,
	totalTRY float64,
	totalEUR float64,
	actor string,
) error {
	if ml == nil {
		return fmt.Errorf("mailer not initialized")
	}
	if pg == nil || mssql == nil {
		return fmt.Errorf("db not initialized")
	}

	// Ensure mapping tables exist (first save can happen before mapping screens are visited).
	if err := ensureFirstGroupMailMappingTables(pg); err != nil {
		return fmt.Errorf("mapping table bootstrap error: %w", err)
	}

	firstGroupCode, _, err := queries.GetProductFirstGroupCodeDescByUrunKodu(ctx, mssql, req.Header.UrunKodu)
	if err != nil {
		return fmt.Errorf("first group resolve error: %w", err)
	}
	if strings.TrimSpace(firstGroupCode) == "" {
		return fmt.Errorf("first group code not found for product")
	}

	recipients, err := loadCostingRecipients(pg, firstGroupCode)
	if err != nil {
		return fmt.Errorf("recipient query error: %w", err)
	}
	if len(recipients) == 0 {
		// Don't hard fail; mapping might be intentionally empty.
		return fmt.Errorf("no costing mail mapping for first group: %s", firstGroupCode)
	}

	// Pull the same header payload used by UI (best-effort) so the mail can show every header label.
	var uiHeader models.ProductionHasCostDetailHeader
	uiHeaderLoaded := false
	if uretim != nil && nOnMLNo > 0 {
		row, err := queries.GetProductionHasCostDetailHeaderByOnMLNo(ctx, uretim, nOnMLNo)
		if err == nil && row != nil {
			// Keep scan fields aligned with GetProductionHasCostDetailHeaderHandler
			if err := row.Scan(
				&uiHeader.UretimiYapanFirma,
				&uiHeader.SonIsEmriVeren,
				&uiHeader.FirmaKodu,
				&uiHeader.NFirmaID,
				&uiHeader.NOnMLNo,
				&uiHeader.UrunKodu,
				&uiHeader.UrunAdi,
				&uiHeader.UretimSekliID,
				&uiHeader.UretimSekli,
				&uiHeader.MaliyetTarihi,
				&uiHeader.DteKayitTarihi,
				&uiHeader.SKullaniciAdi,
				&uiHeader.LTutarTL,
				&uiHeader.LTutarUSD,
				&uiHeader.LTutarEURO,
				&uiHeader.LTutarGBP,
				&uiHeader.SDovizCinsi,
				&uiHeader.LTutarDoviz,
				&uiHeader.DteGuncellemeTarihi,
				&uiHeader.SGuncellemeKullaniciAdi,
				&uiHeader.NUrtReceteID,
			); err == nil {
				uiHeaderLoaded = true
				if mssql != nil {
					ilk, ana, alt, _ := queries.GetProductIlkAnaAltGrupByUrunKodu(ctx, mssql, uiHeader.UrunKodu)
					uiHeader.UrunIlkGrubu = ilk
					uiHeader.UrunAnaGrubu = ana
					uiHeader.UrunAltGrubu = alt
				}
			}
		}
	}

	// Enrich header (fallback) if UI header wasn't loadable.
	ilkGrup, anaGrup, altGrup := "", "", ""
	if uiHeaderLoaded {
		ilkGrup, anaGrup, altGrup = strings.TrimSpace(uiHeader.UrunIlkGrubu), strings.TrimSpace(uiHeader.UrunAnaGrubu), strings.TrimSpace(uiHeader.UrunAltGrubu)
	} else if mssql != nil {
		ilkGrup, anaGrup, altGrup, _ = queries.GetProductIlkAnaAltGrupByUrunKodu(ctx, mssql, req.Header.UrunKodu)
	}

	// Resolve MT bolum titles so we can bucket rows into CEKET/PANTOLON/YELEK/AKSESUAR.
	mtTitleByID := map[int]string{}
	if uretim != nil {
		ids := make([]int, 0, len(req.Detail.Upserts))
		seen := map[int]struct{}{}
		for _, row := range req.Detail.Upserts {
			if row.NUrtMTBolumID <= 0 {
				continue
			}
			if _, ok := seen[row.NUrtMTBolumID]; ok {
				continue
			}
			seen[row.NUrtMTBolumID] = struct{}{}
			ids = append(ids, row.NUrtMTBolumID)
		}
		if len(ids) > 0 {
			vals := make([]string, 0, len(ids))
			args := make([]any, 0, len(ids))
			for i, id := range ids {
				vals = append(vals, fmt.Sprintf("(@p%d)", i+1))
				args = append(args, id)
			}
			q := fmt.Sprintf(`
WITH X AS (SELECT CONVERT(int, V.id) AS id FROM (VALUES %s) AS V(id))
SELECT X.id, LTRIM(RTRIM(ISNULL(M.sAdi,''))) AS title
FROM X
LEFT JOIN dbo.spUrtMTBolum M
  ON M.nUrtMTBolumID = X.id
`, strings.Join(vals, ","))
			rows, err := uretim.QueryContext(ctx, q, args...)
			if err == nil {
				for rows.Next() {
					var id int
					var title string
					if err := rows.Scan(&id, &title); err != nil {
						continue
					}
					mtTitleByID[id] = strings.TrimSpace(title)
				}
				_ = rows.Close()
			}
		}
	}

	// Dynamic part list derived from detail rows.
	preferred := []string{"CEKET", "PANTOLON", "YELEK", "AKSESUAR", "YAKA"}
	seen := map[string]struct{}{}
	dynamic := make([]string, 0, 16)

	for _, row := range req.Detail.Upserts {
		group := strings.ToUpper(strings.TrimSpace(row.SAciklama3))
		included := strings.Contains(group, "CM2") || strings.Contains(group, "CM1") || row.MaliyeteDahil == 1
		if !included {
			continue
		}
		part := ""
		if t := mtTitleByID[row.NUrtMTBolumID]; t != "" {
			part = normalizePartFromMtBolumTitle(t)
		}
		part = strings.TrimSpace(part)
		if part == "" {
			continue
		}
		if _, ok := seen[part]; ok {
			continue
		}
		seen[part] = struct{}{}
		dynamic = append(dynamic, part)
	}

	parts := make([]string, 0, len(dynamic)+len(preferred))
	for _, p := range preferred {
		if _, ok := seen[p]; ok {
			parts = append(parts, p)
		}
	}
	for _, p := range dynamic {
		isPreferred := false
		for _, pref := range preferred {
			if pref == p {
				isPreferred = true
				break
			}
		}
		if !isPreferred {
			parts = append(parts, p)
		}
	}
	if len(parts) == 0 {
		parts = []string{"TANIMSIZ"}
	}
	labor := map[string]*mailBucket{}
	material := map[string]*mailBucket{}
	fabric := map[string]*mailBucket{}
	for _, p := range parts {
		labor[p] = &mailBucket{}
		material[p] = &mailBucket{}
		fabric[p] = &mailBucket{}
	}

	for _, row := range req.Detail.Upserts {
		group := strings.ToUpper(strings.TrimSpace(row.SAciklama3))
		cur := strings.ToUpper(strings.TrimSpace(row.FiyatDoviz))
		qty := row.LMiktar
		if qty < 0 {
			qty = 0
		}
		in := row.FiyatGirilen

		// Included rule: CM2 always; others only when maliyete_dahil = 1.
		included := strings.Contains(group, "CM2") || strings.Contains(group, "CM1") || row.MaliyeteDahil == 1
		if !included {
			continue
		}

		// Part bucket:
		part := ""
		if t := mtTitleByID[row.NUrtMTBolumID]; t != "" {
			part = normalizePartFromMtBolumTitle(t)
		}
		if part == "" {
			continue
		}

		// Convert input to TRY unit
		unitTRY := in
		switch cur {
		case "USD":
			unitTRY = in * usdRate
		case "EUR":
			unitTRY = in * eurRate
		case "GBP":
			unitTRY = in * gbpRate
		case "TRY", "TL", "":
			unitTRY = in
		default:
			unitTRY = in
		}
		unitUSD := 0.0
		if usdRate > 0 {
			unitUSD = unitTRY / usdRate
		}
		amountTRY := unitTRY * qty
		amountUSD := unitUSD * qty

		// input totals for display: inputPrice * qty in input currency
		inputAmount := in * qty

		if strings.Contains(group, "CM2") || strings.Contains(group, "CM1") {
			b := labor[part]
			b.USD += amountUSD
			b.TRY += amountTRY
			addInputAmount(b, cur, inputAmount)
			// UI rule: tick only when cm_price_type_id == 2 (malzemeli). Nil/empty defaults to 1 (unticked).
			if row.CMPriceTypeID != nil && *row.CMPriceTypeID == 2 {
				b.HasCMT = true
			}
			continue
		}
		if group == "DT" || strings.Contains(group, " DT") || group == "TP" || strings.Contains(group, " TP") {
			b := material[part]
			b.USD += amountUSD
			b.TRY += amountTRY
			addInputAmount(b, cur, inputAmount)
			continue
		}
		if group == "FABRIC" || strings.Contains(group, "FABRIC") {
			b := fabric[part]
			b.USD += amountUSD
			b.TRY += amountTRY
			addInputAmount(b, cur, inputAmount)
			// UI parity: fabric summary shows metraj and a representative unit input price (first non-zero).
			if qty > 0 {
				b.MeterQty += qty
				if strings.TrimSpace(b.MeterUom) == "" {
					if u := strings.TrimSpace(row.SBirim); u != "" {
						b.MeterUom = u
					}
				}
			}
			if b.UnitIn <= 0 && in > 0 {
				b.UnitIn = in
				b.UnitCur = cur
			}
		}
	}

	maliyetTarihi := strings.TrimSpace(req.Header.MaliyetTarihi)
	if uiHeaderLoaded {
		// Prefer the UI header date (can differ from "today").
		if v := strings.TrimSpace(uiHeader.MaliyetTarihi); v != "" {
			maliyetTarihi = v
		}
	}
	// Display format: dd.MM.yyyy (mail). Keep the original YYYY-MM-DD for subject readability if present.
	maliyetTarihiLabel := maliyetTarihi
	if parsed, err := time.Parse("2006-01-02", maliyetTarihi); err == nil {
		maliyetTarihiLabel = formatDateTR2(parsed)
	} else if maliyetTarihi == "" {
		maliyetTarihi = time.Now().Format("2006-01-02")
		maliyetTarihiLabel = formatDateTR2(time.Now())
	}

	titleLabel := "MALIYETI GIRIS YAPILAN URUN"
	if isUpdate {
		titleLabel = "MALIYETI GUNCELLENEN URUN"
	}
	subject := fmt.Sprintf("%s | %s | %s | OnML:%d", strings.TrimSpace(req.Header.UrunKodu), titleLabel, maliyetTarihi, nOnMLNo)
	if strings.TrimSpace(actor) != "" {
		subject = fmt.Sprintf("%s tarafindan %s", strings.TrimSpace(actor), subject)
	}

	// Build HTML with 4 tables.
	var b strings.Builder
	b.WriteString(`<div style="font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#1f2a37;">`)
	b.WriteString(fmt.Sprintf(`<h3 style="margin:8px 0 6px;font-size:13px;">%s</h3>`, htmlEsc(titleLabel)))
	b.WriteString(`<table cellpadding="6" cellspacing="0" style="border-collapse:collapse;border:1px solid #e5e7eb;min-width:720px;">`)
	b.WriteString(`<tr style="background:#f3f4f6;font-weight:bold;"><td style="width:220px;">Alan</td><td>Deger</td></tr>`)

	// Prefer resolved UI header (more complete), fallback to request header.
	urunKodu := strings.TrimSpace(req.Header.UrunKodu)
	urunAdi := strings.TrimSpace(req.Header.UrunAdi)
	if uiHeaderLoaded {
		if v := strings.TrimSpace(uiHeader.UrunKodu); v != "" {
			urunKodu = v
		}
		if v := strings.TrimSpace(uiHeader.UrunAdi); v != "" {
			urunAdi = v
		}
	}
	b.WriteString(fmt.Sprintf(`<tr><td>UrunKodu</td><td>%s</td></tr>`, htmlEsc(urunKodu)))
	if urunAdi != "" {
		b.WriteString(fmt.Sprintf(`<tr><td>UrunAdi</td><td>%s</td></tr>`, htmlEsc(urunAdi)))
	}
	if strings.TrimSpace(ilkGrup) != "" || strings.TrimSpace(anaGrup) != "" || strings.TrimSpace(altGrup) != "" {
		if strings.TrimSpace(ilkGrup) != "" {
			b.WriteString(fmt.Sprintf(`<tr><td>Urun Ilk Grubu</td><td>%s</td></tr>`, htmlEsc(ilkGrup)))
		}
		if strings.TrimSpace(anaGrup) != "" {
			b.WriteString(fmt.Sprintf(`<tr><td>Urun Ana Grubu</td><td>%s</td></tr>`, htmlEsc(anaGrup)))
		}
		if strings.TrimSpace(altGrup) != "" {
			b.WriteString(fmt.Sprintf(`<tr><td>Urun Alt Grubu</td><td>%s</td></tr>`, htmlEsc(altGrup)))
		}
	}
	b.WriteString(fmt.Sprintf(`<tr><td>Maliyet Tarihi</td><td>%s</td></tr>`, htmlEsc(maliyetTarihiLabel)))

	// Mirror UI header labels when available:
	if uiHeaderLoaded {
		if strings.TrimSpace(uiHeader.UretimSekli) != "" {
			b.WriteString(fmt.Sprintf(`<tr><td>Uretim Sekli</td><td>%s</td></tr>`, htmlEsc(uiHeader.UretimSekli)))
		} else if strings.TrimSpace(uiHeader.UretimSekliID) != "" {
			b.WriteString(fmt.Sprintf(`<tr><td>Uretim Sekli ID</td><td>%s</td></tr>`, htmlEsc(uiHeader.UretimSekliID)))
		}
		if strings.TrimSpace(uiHeader.UretimiYapanFirma) != "" {
			b.WriteString(fmt.Sprintf(`<tr><td>Uretimi Yapan Firma</td><td>%s</td></tr>`, htmlEsc(uiHeader.UretimiYapanFirma)))
		}
		if strings.TrimSpace(uiHeader.SonIsEmriVeren) != "" {
			b.WriteString(fmt.Sprintf(`<tr><td>2.Firma</td><td>%s</td></tr>`, htmlEsc(uiHeader.SonIsEmriVeren)))
		}
		if strings.TrimSpace(uiHeader.NOnMLNo) != "" {
			b.WriteString(fmt.Sprintf(`<tr><td>nOnMLNo</td><td>%s</td></tr>`, htmlEsc(uiHeader.NOnMLNo)))
		} else {
			b.WriteString(fmt.Sprintf(`<tr><td>nOnMLNo</td><td>%d</td></tr>`, nOnMLNo))
		}
		if strings.TrimSpace(uiHeader.SKullaniciAdi) != "" {
			b.WriteString(fmt.Sprintf(`<tr><td>sKullaniciAdi</td><td>%s</td></tr>`, htmlEsc(uiHeader.SKullaniciAdi)))
		}
		if strings.TrimSpace(uiHeader.DteKayitTarihi) != "" {
			b.WriteString(fmt.Sprintf(`<tr><td>Kayit Tarihi</td><td>%s</td></tr>`, htmlEsc(formatAnyDateTimeTR2(uiHeader.DteKayitTarihi))))
		}
		if strings.TrimSpace(uiHeader.DteGuncellemeTarihi) != "" {
			b.WriteString(fmt.Sprintf(`<tr><td>Son Guncelleme Tarihi</td><td>%s</td></tr>`, htmlEsc(formatAnyDateTimeTR2(uiHeader.DteGuncellemeTarihi))))
		}
		if strings.TrimSpace(uiHeader.SGuncellemeKullaniciAdi) != "" {
			b.WriteString(fmt.Sprintf(`<tr><td>sGuncellemeKullaniciAdi</td><td>%s</td></tr>`, htmlEsc(uiHeader.SGuncellemeKullaniciAdi)))
		}
		if strings.TrimSpace(uiHeader.NUrtReceteID) != "" {
			b.WriteString(fmt.Sprintf(`<tr><td>nUrtReceteID</td><td>%s</td></tr>`, htmlEsc(uiHeader.NUrtReceteID)))
		}
	} else {
		b.WriteString(fmt.Sprintf(`<tr><td>nOnMLNo</td><td>%d</td></tr>`, nOnMLNo))
		if req.Header.NUrtReceteID > 0 {
			b.WriteString(fmt.Sprintf(`<tr><td>nUrtReceteID</td><td>%d</td></tr>`, req.Header.NUrtReceteID))
		}
		if req.Header.UretimSekliID > 0 {
			b.WriteString(fmt.Sprintf(`<tr><td>Uretim Sekli ID</td><td>%d</td></tr>`, req.Header.UretimSekliID))
		}
	}

	// Free text description (from request).
	if strings.TrimSpace(req.Header.SAciklama) != "" {
		b.WriteString(fmt.Sprintf(`<tr><td>Aciklama</td><td>%s</td></tr>`, htmlEsc(req.Header.SAciklama)))
	}

	b.WriteString(`</table>`)

	// 1) Header totals
	b.WriteString(`<h3 style="margin:12px 0 6px;font-size:13px;">Maliyetlere Islenen Toplam Tutar</h3>`)
	b.WriteString(`<table cellpadding="6" cellspacing="0" style="border-collapse:collapse;border:1px solid #e5e7eb;min-width:520px;">`)
	gbpTotal := 0.0
	if gbpRate > 0 {
		gbpTotal = totalTRY / gbpRate
	}
	// UI format (2 rows, key/value pairs)
	b.WriteString(fmt.Sprintf(`<tr><td style="font-weight:bold;color:#374151;">USD</td><td style="text-align:right;">%s</td><td style="font-weight:bold;color:#374151;">TRY</td><td style="text-align:right;">%s</td></tr>`,
		formatMoney2(totalUSD), formatMoney2(totalTRY)))
	b.WriteString(fmt.Sprintf(`<tr><td style="font-weight:bold;color:#374151;">EUR</td><td style="text-align:right;">%s</td><td style="font-weight:bold;color:#374151;">GBP</td><td style="text-align:right;">%s</td></tr>`,
		formatMoney2(totalEUR), formatMoney2(gbpTotal)))
	b.WriteString(`</table>`)

	renderLaborTable := func(title string, m map[string]*mailBucket) {
		b.WriteString(fmt.Sprintf(`<h3 style="margin:14px 0 6px;font-size:13px;">%s</h3>`, htmlEsc(title)))
		b.WriteString(`<table cellpadding="6" cellspacing="0" style="border-collapse:collapse;border:1px solid #e5e7eb;min-width:720px;">`)
		b.WriteString(`<tr style="background:#f3f4f6;font-weight:bold;">`)
		b.WriteString(`<td>Parca</td><td style="text-align:right;">Giris</td><td>Pr.Br.</td><td style="text-align:right;">USD Tutar</td><td style="text-align:right;">TRY Tutar</td><td style="text-align:center;">CMT/Malzemeli</td>`)
		b.WriteString(`</tr>`)
		totalUSD := 0.0
		totalTRY := 0.0
		for _, p := range parts {
			row := m[p]
			inAmt, inCur := summarizeInputByCurrency(row)
			tick := ""
			if row != nil && row.HasCMT {
				tick = "&#10003;"
			}
			if row != nil {
				totalUSD += row.USD
				totalTRY += row.TRY
			}
			b.WriteString(`<tr>`)
			b.WriteString(fmt.Sprintf(`<td>%s</td>`, htmlEsc(p)))
			b.WriteString(fmt.Sprintf(`<td style="text-align:right;">%s</td>`, htmlEsc(inAmt)))
			b.WriteString(fmt.Sprintf(`<td>%s</td>`, htmlEsc(inCur)))
			b.WriteString(fmt.Sprintf(`<td style="text-align:right;">%s</td>`, formatMoney2(row.USD)))
			b.WriteString(fmt.Sprintf(`<td style="text-align:right;">%s</td>`, formatMoney2(row.TRY)))
			b.WriteString(fmt.Sprintf(`<td style="text-align:center;">%s</td>`, tick))
			b.WriteString(`</tr>`)
		}
		b.WriteString(`<tr style="border-top:2px solid #e5e7eb;font-weight:bold;background:#fbfbfd;">`)
		b.WriteString(`<td>TOPLAM</td><td></td><td></td>`)
		b.WriteString(fmt.Sprintf(`<td style="text-align:right;">%s</td>`, formatMoney2(totalUSD)))
		b.WriteString(fmt.Sprintf(`<td style="text-align:right;">%s</td>`, formatMoney2(totalTRY)))
		b.WriteString(`<td></td>`)
		b.WriteString(`</tr>`)
		b.WriteString(`</table>`)
	}

	renderMaterialTable := func(title string, m map[string]*mailBucket) {
		b.WriteString(fmt.Sprintf(`<h3 style="margin:14px 0 6px;font-size:13px;">%s</h3>`, htmlEsc(title)))
		b.WriteString(`<table cellpadding="6" cellspacing="0" style="border-collapse:collapse;border:1px solid #e5e7eb;min-width:520px;">`)
		b.WriteString(`<tr style="background:#f3f4f6;font-weight:bold;"><td>Parca</td><td style="text-align:right;">USD Tutar</td><td style="text-align:right;">TRY Tutar</td></tr>`)
		totalUSD := 0.0
		totalTRY := 0.0
		for _, p := range parts {
			row := m[p]
			if row != nil {
				totalUSD += row.USD
				totalTRY += row.TRY
			}
			b.WriteString(`<tr>`)
			b.WriteString(fmt.Sprintf(`<td>%s</td>`, htmlEsc(p)))
			b.WriteString(fmt.Sprintf(`<td style="text-align:right;">%s</td>`, formatMoney2(row.USD)))
			b.WriteString(fmt.Sprintf(`<td style="text-align:right;">%s</td>`, formatMoney2(row.TRY)))
			b.WriteString(`</tr>`)
		}
		b.WriteString(`<tr style="border-top:2px solid #e5e7eb;font-weight:bold;background:#fbfbfd;">`)
		b.WriteString(`<td>TOPLAM</td>`)
		b.WriteString(fmt.Sprintf(`<td style="text-align:right;">%s</td>`, formatMoney2(totalUSD)))
		b.WriteString(fmt.Sprintf(`<td style="text-align:right;">%s</td>`, formatMoney2(totalTRY)))
		b.WriteString(`</tr>`)
		b.WriteString(`</table>`)
	}

	renderFabricTable := func(title string, m map[string]*mailBucket) {
		b.WriteString(fmt.Sprintf(`<h3 style="margin:14px 0 6px;font-size:13px;">%s</h3>`, htmlEsc(title)))
		b.WriteString(`<table cellpadding="6" cellspacing="0" style="border-collapse:collapse;border:1px solid #e5e7eb;min-width:720px;">`)
		b.WriteString(`<tr style="background:#f3f4f6;font-weight:bold;">`)
		b.WriteString(`<td>Parca</td><td style="text-align:right;">Metraj</td><td style="text-align:right;">MT Giris Fiyat</td><td>Pr.Br.</td><td style="text-align:right;">USD Tutar</td><td style="text-align:right;">TRY Tutar</td>`)
		b.WriteString(`</tr>`)
		totalUSD := 0.0
		totalTRY := 0.0
		totalMeter := 0.0
		for _, p := range parts {
			row := m[p]
			inAmt, inCur := summarizeInputByCurrency(row)
			_ = inAmt
			unitLabel := "-"
			curLabel := inCur
			if row != nil && row.UnitIn > 0 {
				unitLabel = formatMoney2(row.UnitIn)
				if strings.TrimSpace(row.UnitCur) != "" {
					curLabel = strings.ToUpper(strings.TrimSpace(row.UnitCur))
				}
			}
			if strings.TrimSpace(curLabel) == "" {
				curLabel = "-"
			}
			if row != nil {
				totalUSD += row.USD
				totalTRY += row.TRY
				totalMeter += row.MeterQty
			}
			b.WriteString(`<tr>`)
			b.WriteString(fmt.Sprintf(`<td>%s</td>`, htmlEsc(p)))
			b.WriteString(fmt.Sprintf(`<td style="text-align:right;">%s</td>`, htmlEsc(formatMeterLabel(row))))
			b.WriteString(fmt.Sprintf(`<td style="text-align:right;">%s</td>`, htmlEsc(unitLabel)))
			b.WriteString(fmt.Sprintf(`<td>%s</td>`, htmlEsc(curLabel)))
			b.WriteString(fmt.Sprintf(`<td style="text-align:right;">%s</td>`, formatMoney2(row.USD)))
			b.WriteString(fmt.Sprintf(`<td style="text-align:right;">%s</td>`, formatMoney2(row.TRY)))
			b.WriteString(`</tr>`)
		}
		totalMeterLabel := "-"
		if totalMeter > 0 {
			totalMeterLabel = fmt.Sprintf("%s MT", formatQty2(totalMeter))
		}
		b.WriteString(`<tr style="border-top:2px solid #e5e7eb;font-weight:bold;background:#fbfbfd;">`)
		b.WriteString(`<td>TOPLAM</td>`)
		b.WriteString(fmt.Sprintf(`<td style="text-align:right;">%s</td>`, htmlEsc(totalMeterLabel)))
		b.WriteString(`<td></td><td></td>`)
		b.WriteString(fmt.Sprintf(`<td style="text-align:right;">%s</td>`, formatMoney2(totalUSD)))
		b.WriteString(fmt.Sprintf(`<td style="text-align:right;">%s</td>`, formatMoney2(totalTRY)))
		b.WriteString(`</tr>`)
		b.WriteString(`</table>`)
	}

	renderLaborTable("Iscilik Fiyatlari (CM2)", labor)
	renderMaterialTable("Malzeme Fiyatlari (DT/TP, maliyete dahil)", material)
	renderFabricTable("Kumas Fiyatlari (FABRIC, maliyete dahil)", fabric)

	b.WriteString(`<p style="margin-top:10px;color:#6b7280;"><i>Bu mail BaggiSS App uzerinden otomatik gonderilmistir.</i></p>`)
	b.WriteString(`</div>`)

	msg := mailer.Message{
		To:       recipients,
		Subject:  subject,
		BodyHTML: b.String(),
	}

	// Graph send
	if err := ml.Send(context.Background(), msg); err != nil {
		return err
	}
	return nil
}
