package routes

import (
	"bytes"
	"context"
	"database/sql"
	"fmt"
	"log"
	"sort"
	"strings"
	"time"

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

	"github.com/jung-kurt/gofpdf"
	"github.com/lib/pq"
)

type wholesaleCampaignMailRow struct {
	ProductCode   string
	UrunIlkGrubu  string
	Marka         string
	BrandGroupSec string
	Dim1          int64
	Dim3          int64
	Dim1Token     string
	Dim3Token     string
	CampaignCode  string
	CampaignTitle string
	DiscountRate  float64
}

func buildWholesaleCampaignChangeMailHTML(rows []wholesaleCampaignMailRow, actor string, at time.Time) string {
	var b strings.Builder
	b.WriteString(`<div style="font-family:Segoe UI, Arial, sans-serif; font-size:18px; line-height:1.35; -webkit-text-size-adjust:100%;">`)
	b.WriteString(`<div style="margin-bottom:10px;">`)
	b.WriteString(`<div style="font-size:22px; margin-bottom:4px;"><b>Kampanya Degisikligi</b></div>`)
	if strings.TrimSpace(actor) != "" {
		b.WriteString(`<div>Islem Yapan: <b>` + htmlEscapeMini(actor) + `</b></div>`)
	}
	b.WriteString(`<div>Tarih: <b>` + htmlEscapeMini(at.Format("02.01.2006 15:04")) + `</b></div>`)
	b.WriteString(`<div>Varyant Sayisi: <b>` + fmt.Sprintf("%d", len(rows)) + `</b></div>`)
	b.WriteString(`<div style="margin-top:10px;">Detaylar ekteki PDF dosyasindadir.</div>`)
	b.WriteString(`</div>`)
	b.WriteString(`</div>`)
	return b.String()
}

func buildWholesaleCampaignChangeMailPDF(rows []wholesaleCampaignMailRow, actor string, at time.Time) ([]byte, error) {
	pdf := gofpdf.New("L", "mm", "A4", "")
	font := "Arial"
	if err := registerDejavuFonts(pdf, "dejavu"); err == nil {
		font = "dejavu"
	} else {
		log.Printf("[campaign-mail] pdf font fallback: %v", err)
	}
	pdf.SetMargins(7, 8, 7)
	pdf.SetAutoPageBreak(true, 10)
	pdf.AddPage()
	pdf.SetFont(font, "B", 13)
	pdf.CellFormat(0, 7, "Kampanya Degisikligi", "", 1, "L", false, 0, "")
	pdf.SetFont(font, "", 8)
	if strings.TrimSpace(actor) != "" {
		pdf.CellFormat(0, 5, "Islem Yapan: "+strings.TrimSpace(actor), "", 1, "L", false, 0, "")
	}
	pdf.CellFormat(0, 5, "Tarih: "+at.Format("02.01.2006 15:04"), "", 1, "L", false, 0, "")
	pdf.CellFormat(0, 5, fmt.Sprintf("Varyant Sayisi: %d", len(rows)), "", 1, "L", false, 0, "")
	pdf.Ln(2)

	heads := []string{
		"MARKA GRUBU",
		"MARKA",
		"URUN KODU",
		"DIM1",
		"DIM3",
		"KAMPANYA",
		"IND %",
	}
	widths := []float64{35, 26, 35, 20, 20, 118, 16}
	aligns := []string{"L", "L", "L", "R", "R", "L", "R"}
	writePDFTableHeader(pdf, font, heads, widths)

	pdf.SetFont(font, "", 7)
	for _, r := range rows {
		if pdf.GetY() > 190 {
			pdf.AddPage()
			writePDFTableHeader(pdf, font, heads, widths)
			pdf.SetFont(font, "", 7)
		}
		campaignLabel := strings.TrimSpace(r.CampaignCode)
		if t := strings.TrimSpace(r.CampaignTitle); t != "" {
			if campaignLabel != "" {
				campaignLabel = campaignLabel + " - " + t
			} else {
				campaignLabel = t
			}
		}
		cells := []string{
			r.BrandGroupSec,
			r.Marka,
			r.ProductCode,
			strings.TrimSpace(r.Dim1Token),
			func() string {
				if strings.TrimSpace(r.Dim3Token) != "" {
					return strings.TrimSpace(r.Dim3Token)
				}
				return ""
			}(),
			campaignLabel,
			fmt.Sprintf("%.2f", r.DiscountRate),
		}
		writePDFTableRow(pdf, cells, widths, aligns, 5)
	}

	var buf bytes.Buffer
	if err := pdf.Output(&buf); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

// sendWholesaleCampaignChangeMails sends one mail per UrunIlkGrubu using existing pricing mail mapping tables.
// It lists only variants that currently have a campaign assigned.
func sendWholesaleCampaignChangeMails(bg context.Context, ml *mailer.GraphMailer, productCodes []string, actor string) {
	if ml == nil {
		return
	}
	pg := db.PgDB
	if pg == nil {
		log.Printf("[campaign-mail] skipped: pg not ready")
		return
	}
	// Ensure mapping tables exist (reuse pricing mapping).
	if err := ensureFirstGroupMailMappingTables(pg); err != nil {
		log.Printf("[campaign-mail] mapping bootstrap error: %v", err)
		return
	}

	ctx, cancel := context.WithTimeout(bg, 90*time.Second)
	defer cancel()

	codes := make([]string, 0, len(productCodes))
	seen := map[string]struct{}{}
	for _, c := range productCodes {
		c = strings.TrimSpace(c)
		if c == "" {
			continue
		}
		if _, ok := seen[c]; ok {
			continue
		}
		seen[c] = struct{}{}
		codes = append(codes, c)
	}
	if len(codes) == 0 {
		return
	}

	// Product info for grouping (UrunIlkGrubu, Marka, BrandGroupSec) comes from Nebim query.
	// This is best-effort: if MSSQL is down, we still send a single mail under group "UNKNOWN".
	productInfo := map[string]models.ProductPricing{}
	{
		rows, err := queries.GetAllProductPricingRows(ctx, 500, queries.ProductPricingFilters{ProductCode: codes}, "productCode", false)
		if err == nil {
			for _, r := range rows {
				code := strings.TrimSpace(r.ProductCode)
				if code != "" {
					productInfo[code] = r
				}
			}
		}
	}

	type dbRow struct {
		ProductCode   string
		Dim1          int64
		Dim3          sql.NullInt64
		CampaignCode  string
		CampaignTitle string
		DiscountRate  float64
	}

	rows, err := pg.QueryContext(ctx, `
WITH mm AS (
  SELECT id AS mmitem_id, code
  FROM mmitem
  WHERE code = ANY($1::text[])
),
latest AS (
  SELECT DISTINCT ON (z.mmitem_id, z.dim1, COALESCE(z.dim3, 0))
    z.mmitem_id,
    z.dim1,
    z.dim3,
    z.sdcampaign_id
  FROM zbggcampaign z
  JOIN mm ON mm.mmitem_id = z.mmitem_id
  ORDER BY z.mmitem_id, z.dim1, COALESCE(z.dim3, 0), z.id DESC
)
SELECT
  mm.code AS product_code,
  l.dim1,
  l.dim3,
  COALESCE(sc.code,'') AS campaign_code,
  COALESCE(sc.title,'') AS campaign_title,
  COALESCE(sc.discount_rate, 0)::float8 AS discount_rate
FROM mm
JOIN latest l
  ON l.mmitem_id = mm.mmitem_id
JOIN sdcampaign sc
  ON sc.id = l.sdcampaign_id
WHERE COALESCE(sc.is_active, TRUE) = TRUE
ORDER BY mm.code, l.dim1, COALESCE(l.dim3, 0), sc.discount_rate DESC;
`, pq.Array(codes))
	if err != nil {
		log.Printf("[campaign-mail] campaign rows query error: %v", err)
		return
	}
	defer rows.Close()

	// Resolve dim ids -> human tokens so mails show Nebim-style variant codes (not numeric ids).
	resolveDimTokens := func(ctx context.Context, dimIDs []int64) map[int64]string {
		out := make(map[int64]string, len(dimIDs))
		uniq := make([]int64, 0, len(dimIDs))
		seen := make(map[int64]struct{}, len(dimIDs))
		for _, id := range dimIDs {
			if id <= 0 {
				continue
			}
			if _, ok := seen[id]; ok {
				continue
			}
			seen[id] = struct{}{}
			uniq = append(uniq, id)
		}
		if len(uniq) == 0 {
			return out
		}
		tRows, err := pg.QueryContext(ctx, `
SELECT DISTINCT ON (dim_id)
  dim_id,
  token
FROM mk_dim_token_map
WHERE dim_column = 'dimval1'
  AND dim_id = ANY($1::bigint[])
ORDER BY dim_id, updated_at DESC;
`, pq.Array(uniq))
		if err != nil {
			return out
		}
		defer tRows.Close()
		for tRows.Next() {
			var id int64
			var tok string
			if err := tRows.Scan(&id, &tok); err == nil {
				tok = strings.TrimSpace(tok)
				if tok != "" {
					out[id] = tok
				}
			}
		}
		return out
	}

	mailRows := make([]wholesaleCampaignMailRow, 0, 1024)
	dimIDs := make([]int64, 0, 2048)
	for rows.Next() {
		var r dbRow
		if err := rows.Scan(&r.ProductCode, &r.Dim1, &r.Dim3, &r.CampaignCode, &r.CampaignTitle, &r.DiscountRate); err != nil {
			log.Printf("[campaign-mail] scan error: %v", err)
			return
		}
		code := strings.TrimSpace(r.ProductCode)
		info := productInfo[code]
		group := strings.TrimSpace(info.UrunIlkGrubu)
		if group == "" {
			group = "UNKNOWN"
		}
		d3 := int64(0)
		if r.Dim3.Valid {
			d3 = r.Dim3.Int64
		}
		if r.Dim1 > 0 {
			dimIDs = append(dimIDs, r.Dim1)
		}
		if d3 > 0 {
			dimIDs = append(dimIDs, d3)
		}
		mailRows = append(mailRows, wholesaleCampaignMailRow{
			ProductCode:   code,
			UrunIlkGrubu:  group,
			Marka:         strings.TrimSpace(info.Marka),
			BrandGroupSec: strings.TrimSpace(info.BrandGroupSec),
			Dim1:          r.Dim1,
			Dim3:          d3,
			CampaignCode:  strings.TrimSpace(r.CampaignCode),
			CampaignTitle: strings.TrimSpace(r.CampaignTitle),
			DiscountRate:  r.DiscountRate,
		})
	}
	if err := rows.Err(); err != nil {
		log.Printf("[campaign-mail] rows error: %v", err)
		return
	}

	if len(mailRows) == 0 {
		// Nothing assigned => no mail.
		return
	}

	tokens := resolveDimTokens(ctx, dimIDs)
	for i := range mailRows {
		mailRows[i].Dim1Token = tokens[mailRows[i].Dim1]
		if mailRows[i].Dim1Token == "" && mailRows[i].Dim1 > 0 {
			mailRows[i].Dim1Token = fmt.Sprintf("%d", mailRows[i].Dim1)
		}
		if mailRows[i].Dim3 > 0 {
			mailRows[i].Dim3Token = tokens[mailRows[i].Dim3]
			if mailRows[i].Dim3Token == "" {
				mailRows[i].Dim3Token = fmt.Sprintf("%d", mailRows[i].Dim3)
			}
		}
	}

	byGroup := map[string][]wholesaleCampaignMailRow{}
	for _, r := range mailRows {
		g := strings.TrimSpace(r.UrunIlkGrubu)
		if g == "" {
			g = "UNKNOWN"
		}
		byGroup[g] = append(byGroup[g], r)
	}

	now := time.Now()
	for group, list := range byGroup {
		recipients, err := loadPricingRecipients(pg, group)
		if err != nil {
			log.Printf("[campaign-mail] recipient query error group=%s err=%v", group, err)
			continue
		}
		if len(recipients) == 0 {
			log.Printf("[campaign-mail] no recipients mapped group=%s", group)
			continue
		}

		sort.Slice(list, func(i, j int) bool {
			if list[i].ProductCode != list[j].ProductCode {
				return list[i].ProductCode < list[j].ProductCode
			}
			if list[i].Dim1 != list[j].Dim1 {
				return list[i].Dim1 < list[j].Dim1
			}
			return list[i].Dim3 < list[j].Dim3
		})

		subject := fmt.Sprintf("Kampanya Degisikligi | %s | %d varyant", now.Format("02.01.2006 15:04"), len(list))
		html := buildWholesaleCampaignChangeMailHTML(list, actor, now)
		attachments := []mailer.Attachment(nil)
		if data, err := buildWholesaleCampaignChangeMailPDF(list, actor, now); err != nil {
			log.Printf("[campaign-mail] pdf build failed group=%s err=%v", group, err)
		} else {
			attachments = append(attachments, mailer.Attachment{
				FileName:    fmt.Sprintf("kampanya-degisikligi-%s.pdf", now.Format("20060102-1504")),
				ContentType: "application/pdf",
				Data:        data,
			})
		}

		stepCtx, stepCancel := context.WithTimeout(bg, 25*time.Second)
		err = ml.Send(stepCtx, mailer.Message{
			To:          recipients,
			Subject:     subject,
			BodyHTML:    html,
			Attachments: attachments,
		})
		stepCancel()
		if err != nil {
			log.Printf("[campaign-mail] send failed group=%s err=%v", group, err)
		} else {
			log.Printf("[campaign-mail] sent group=%s to=%d variants=%d", group, len(recipients), len(list))
		}
	}
}
