package routes

import (
	"bytes"
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strings"

	"bssapp-backend/auth"
	"bssapp-backend/internal/mailer"
)

type sendOrderMarketMailPayload struct {
	OrderHeaderID   string                       `json:"orderHeaderID"`
	Operation       string                       `json:"operation"`
	DeletedItems    []string                     `json:"deletedItems"`
	UpdatedItems    []string                     `json:"updatedItems"`
	AddedItems      []string                     `json:"addedItems"`
	OldDueDate      string                       `json:"oldDueDate"`
	NewDueDate      string                       `json:"newDueDate"`
	ExtraRecipients []string                     `json:"extraRecipients"`
	DueDateChanges  []sendOrderMailDueDateChange `json:"dueDateChanges"`
}

type sendOrderMailDueDateChange struct {
	ItemCode     string `json:"itemCode"`
	ColorCode    string `json:"colorCode"`
	ItemDim2Code string `json:"itemDim2Code"`
	OldDueDate   string `json:"oldDueDate"`
	NewDueDate   string `json:"newDueDate"`
}

func SendOrderMarketMailHandler(pg *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")

		if ml == nil {
			http.Error(w, "mailer not initialized", http.StatusServiceUnavailable)
			return
		}
		if pg == nil || mssql == nil {
			http.Error(w, "database not initialized", http.StatusInternalServerError)
			return
		}
		claims, ok := auth.GetClaimsFromContext(r.Context())
		if !ok || claims == nil {
			http.Error(w, "unauthorized", http.StatusUnauthorized)
			return
		}

		var payload sendOrderMarketMailPayload
		if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
			http.Error(w, "invalid payload", http.StatusBadRequest)
			return
		}

		orderID := strings.TrimSpace(payload.OrderHeaderID)
		if orderID == "" {
			http.Error(w, "orderHeaderID is required", http.StatusBadRequest)
			return
		}

		orderNo, currAccCode, marketCode, marketTitle, err := resolveOrderMailContext(mssql, orderID)
		if err != nil {
			http.Error(w, "order context error: "+err.Error(), http.StatusInternalServerError)
			return
		}
		if strings.TrimSpace(marketCode) == "" && strings.TrimSpace(marketTitle) == "" {
			http.Error(w, "market not found for order/cari", http.StatusBadRequest)
			return
		}

		recipients, err := loadMarketRecipients(pg, marketCode, marketTitle)
		if err != nil {
			http.Error(w, "recipient query error: "+err.Error(), http.StatusInternalServerError)
			return
		}
		recipients = appendUniqueRecipients(recipients, payload.ExtraRecipients...)
		if len(recipients) == 0 {
			http.Error(w, "no active email mapping for market", http.StatusBadRequest)
			return
		}

		pdfBytes, header, err := buildOrderPDFBytesForMail(mssql, pg, orderID)
		if err != nil {
			http.Error(w, "pdf build error: "+err.Error(), http.StatusInternalServerError)
			return
		}

		number := strings.TrimSpace(orderNo)
		if number == "" && header != nil {
			number = strings.TrimSpace(header.OrderNumber)
		}
		if number == "" {
			number = orderID
		}

		marketLabel := strings.TrimSpace(marketTitle)
		if marketLabel == "" {
			marketLabel = strings.TrimSpace(marketCode)
		}

		actor := strings.TrimSpace(claims.Username)
		if actor == "" {
			actor = strings.TrimSpace(claims.V3Username)
		}
		if actor == "" {
			actor = "Bilinmeyen Kullanici"
		}

		op := strings.ToLower(strings.TrimSpace(payload.Operation))
		isUpdate := op == "update"

		subjectAction := "SİPARİŞ KAYDI OLUŞTURULDU"
		if isUpdate {
			subjectAction = "SİPARİŞ GÜNCELLENDİ."
		}
		if payload.NewDueDate != "" && payload.OldDueDate != payload.NewDueDate {
			subjectAction = "SİPARİŞ TERMİNİ GÜNCELLENDİ."
		}
		if isUpdate && subjectAction == "SİPARİŞ GÜNCELLENDİ." {
			// Satır bazlı termin kontrolü
			for _, item := range payload.UpdatedItems {
				if strings.Contains(item, "Termin:") {
					subjectAction = "SİPARİŞ TERMİNİ GÜNCELLENDİ."
					break
				}
			}
		}
		subject := fmt.Sprintf("%s kullanıcısı tarafından %s %s", actor, number, subjectAction)

		cariDetail := ""
		customerRep := ""
		if header != nil {
			cariDetail = strings.TrimSpace(header.CurrAccName)
			customerRep = strings.TrimSpace(header.CustomerRep)
		}

		body := make([]string, 0, 12)
		body = append(body,
			`<p>`,
			fmt.Sprintf(`<b>Cari Kodu:</b> %s<br/>`, htmlEsc(currAccCode)),
			fmt.Sprintf(`<b>Cari Detay:</b> %s<br/>`, htmlEsc(cariDetail)),
			fmt.Sprintf(`<b>Müşteri Temsilcisi:</b> %s<br/>`, htmlEsc(customerRep)),
			fmt.Sprintf(`<b>Piyasa:</b> %s`, htmlEsc(marketLabel)),
			`</p>`,
		)

		if payload.NewDueDate != "" && payload.OldDueDate != payload.NewDueDate {
			body = append(body,
				fmt.Sprintf(`<p><b>Termin Değişikliği:</b> %s &rarr; <b style="color:red">%s</b></p>`,
					htmlEsc(payload.OldDueDate), htmlEsc(payload.NewDueDate)),
			)
		}

		if isUpdate {
			body = append(body,
				renderItemListHTML("Silinen Ürün Kodları", payload.DeletedItems),
				renderItemListHTML("Güncellenen Ürün Kodları", payload.UpdatedItems),
				renderItemListHTML("Eklenen Ürün Kodları", payload.AddedItems),
			)
		}

		body = append(body, `<p><i>Bu sipariş BaggiSS App Uygulamasından oluşturulmuştur.</i></p>`)
		body = append(body, `<p>PDF ektedir.</p>`)
		if dueDateTableHTML := renderDueDateChangesTableHTML("Termin DeÄŸiÅŸiklikleri", payload.DueDateChanges); dueDateTableHTML != "" {
			body = append(body, dueDateTableHTML)
		}

		bodyHTML := strings.Join(body, "\n")

		fileNo := sanitizeFileName(number)
		if fileNo == "" {
			fileNo = orderID
		}

		msg := mailer.Message{
			To:       recipients,
			Subject:  subject,
			BodyHTML: bodyHTML,
			Attachments: []mailer.Attachment{
				{
					FileName:    "ORDER_" + fileNo + ".pdf",
					ContentType: "application/pdf",
					Data:        pdfBytes,
				},
			},
		}

		if err := ml.Send(context.Background(), msg); err != nil {
			http.Error(w, "mail send error: "+err.Error(), http.StatusInternalServerError)
			return
		}

		_ = json.NewEncoder(w).Encode(map[string]any{
			"success":       true,
			"orderHeaderID": orderID,
			"orderNumber":   number,
			"marketCode":    marketCode,
			"marketTitle":   marketTitle,
			"recipients":    recipients,
			"sentCount":     len(recipients),
		})
	}
}

func resolveOrderMailContext(db *sql.DB, orderID string) (orderNo, currAccCode, marketCode, marketTitle string, err error) {
	row := db.QueryRow(`
SELECT TOP (1)
  ISNULL(h.OrderNumber, ''),
  ISNULL(h.CurrAccCode, ''),
  ISNULL(LTRIM(RTRIM(f.CustomerAtt01)), '') AS MarketCode,
  ISNULL(py.AttributeDescription, '') AS MarketTitle
FROM BAGGI_V3.dbo.trOrderHeader h
LEFT JOIN BAGGI_V3.dbo.CustomerAttributesFilter f
  ON f.CurrAccCode = h.CurrAccCode
LEFT JOIN BAGGI_V3.dbo.cdCurrAccAttributeDesc py
  ON py.CurrAccTypeCode = h.CurrAccTypeCode
 AND py.AttributeTypeCode = 1
 AND py.AttributeCode = f.CustomerAtt01
 AND py.LangCode = 'TR'
WHERE CAST(h.OrderHeaderID AS varchar(36)) = @p1
`, orderID)

	var no, cari, pCode, pTitle string
	if err = row.Scan(&no, &cari, &pCode, &pTitle); err != nil {
		return "", "", "", "", err
	}

	no = strings.TrimSpace(no)
	cari = strings.TrimSpace(cari)
	pCode = strings.TrimSpace(pCode)
	pTitle = strings.TrimSpace(pTitle)

	return no, cari, pCode, pTitle, nil
}

func loadMarketRecipients(pg *sql.DB, marketCode, marketTitle string) ([]string, error) {
	rows, err := pg.Query(`
SELECT DISTINCT TRIM(m.email) AS email
FROM mk_sales_piy p
JOIN mk_market_mail mm
  ON mm.market_id = p.id
JOIN mk_mail m
  ON m.id = mm.mail_id
WHERE p.is_active = true
  AND m.is_active = true
  AND (
    UPPER(TRIM(p.code)) = UPPER(TRIM($1))
    OR ($2 <> '' AND UPPER(TRIM(p.title)) = UPPER(TRIM($2)))
  )
  AND COALESCE(TRIM(m.email), '') <> ''
ORDER BY email
`, strings.TrimSpace(marketCode), strings.TrimSpace(marketTitle))
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	out := make([]string, 0, 8)
	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)
		}
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return out, nil
}

func appendUniqueRecipients(base []string, extras ...string) []string {
	seen := make(map[string]struct{}, len(base)+len(extras))
	out := make([]string, 0, len(base)+len(extras))

	for _, raw := range base {
		mail := strings.ToLower(strings.TrimSpace(raw))
		if mail == "" {
			continue
		}
		if _, ok := seen[mail]; ok {
			continue
		}
		seen[mail] = struct{}{}
		out = append(out, mail)
	}

	for _, raw := range extras {
		mail := strings.ToLower(strings.TrimSpace(raw))
		if mail == "" {
			continue
		}
		if _, ok := seen[mail]; ok {
			continue
		}
		seen[mail] = struct{}{}
		out = append(out, mail)
	}

	return out
}

func buildOrderPDFBytesForMail(db *sql.DB, pgDB *sql.DB, orderID string) ([]byte, *OrderHeader, error) {
	header, err := getOrderHeaderFromDB(db, orderID)
	if err != nil {
		return nil, nil, err
	}

	lines, err := getOrderLinesFromDB(db, orderID)
	if err != nil {
		return nil, nil, err
	}

	hasVat := false
	var vatRate float64
	for _, l := range lines {
		if l.VatRate.Valid && l.VatRate.Float64 > 0 {
			hasVat = true
			vatRate = l.VatRate.Float64
			break
		}
	}

	if pgDB == nil {
		return nil, nil, errors.New("product-size-match db not initialized")
	}
	sizeMatchData, err := loadProductSizeMatchData(pgDB)
	if err != nil {
		return nil, nil, err
	}
	rows := normalizeOrderLinesForPdf(lines, sizeMatchData)
	for i := range rows {
		if strings.TrimSpace(rows[i].Category) != "" {
			continue
		}
		bedenList := make([]string, 0, len(rows[i].SizeQty))
		for s := range rows[i].SizeQty {
			bedenList = append(bedenList, s)
		}
		rows[i].Category = detectBedenGroupGo(
			sizeMatchData,
			bedenList,
			rows[i].GroupMain,
			rows[i].GroupSub,
			rows[i].YetiskinGarson,
			rows[i].YetiskinGarson,
		)
		if strings.TrimSpace(rows[i].Category) == "" {
			rows[i].Category = catTak
		}
	}

	pdf, err := newOrderPdf()
	if err != nil {
		return nil, nil, err
	}

	renderOrderGrid(pdf, header, rows, hasVat, vatRate)
	if err := pdf.Error(); err != nil {
		return nil, nil, err
	}

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

	return buf.Bytes(), header, nil
}

func sanitizeFileName(v string) string {
	s := strings.TrimSpace(v)
	if s == "" {
		return ""
	}
	invalid := []string{`\\`, `/`, `:`, `*`, `?`, `"`, `<`, `>`, `|`}
	for _, bad := range invalid {
		s = strings.ReplaceAll(s, bad, "_")
	}
	return strings.TrimSpace(s)
}

func htmlEsc(s string) string {
	r := strings.NewReplacer(
		"&", "&amp;",
		"<", "&lt;",
		">", "&gt;",
		`"`, "&quot;",
		"'", "&#39;",
	)
	return r.Replace(s)
}

func renderItemListHTML(title string, items []string) string {
	clean := make([]string, 0, len(items))
	seen := make(map[string]struct{}, len(items))
	for _, raw := range items {
		v := strings.TrimSpace(raw)
		if v == "" {
			continue
		}
		if _, ok := seen[v]; ok {
			continue
		}
		seen[v] = struct{}{}
		clean = append(clean, v)
	}

	if len(clean) == 0 {
		return fmt.Sprintf(`<p><b>%s:</b> Yok</p>`, htmlEsc(title))
	}

	b := make([]string, 0, len(clean)+3)
	b = append(b, fmt.Sprintf(`<p><b>%s:</b><br/>`, htmlEsc(title)))
	for _, item := range clean {
		b = append(b, "- "+htmlEsc(item)+"<br/>")
	}
	b = append(b, `</p>`)
	return strings.Join(b, "\n")
}

func renderDueDateChangesTableHTML(title string, rows []sendOrderMailDueDateChange) string {
	if len(rows) == 0 {
		return ""
	}

	seen := make(map[string]struct{}, len(rows))
	clean := make([]sendOrderMailDueDateChange, 0, len(rows))
	for _, row := range rows {
		itemCode := strings.TrimSpace(row.ItemCode)
		colorCode := strings.TrimSpace(row.ColorCode)
		itemDim2Code := strings.TrimSpace(row.ItemDim2Code)
		oldDueDate := strings.TrimSpace(row.OldDueDate)
		newDueDate := strings.TrimSpace(row.NewDueDate)
		if itemCode == "" || newDueDate == "" || oldDueDate == newDueDate {
			continue
		}
		key := strings.ToUpper(strings.Join([]string{itemCode, colorCode, itemDim2Code, oldDueDate, newDueDate}, "|"))
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		clean = append(clean, sendOrderMailDueDateChange{
			ItemCode:     itemCode,
			ColorCode:    colorCode,
			ItemDim2Code: itemDim2Code,
			OldDueDate:   oldDueDate,
			NewDueDate:   newDueDate,
		})
	}

	if len(clean) == 0 {
		return ""
	}

	var b strings.Builder
	b.WriteString(fmt.Sprintf(`<p><b>%s:</b></p>`, htmlEsc(title)))
	b.WriteString(`<table border="1" cellpadding="5" style="border-collapse: collapse; width: 100%;">`)
	b.WriteString(`<tr style="background-color: #f2f2f2;"><th>Ürün Kodu</th><th>Renk</th><th>2. Renk</th><th>Eski Termin</th><th>Yeni Termin</th></tr>`)
	for _, row := range clean {
		b.WriteString("<tr>")
		b.WriteString(fmt.Sprintf("<td>%s</td>", htmlEsc(row.ItemCode)))
		b.WriteString(fmt.Sprintf("<td>%s</td>", htmlEsc(row.ColorCode)))
		b.WriteString(fmt.Sprintf("<td>%s</td>", htmlEsc(row.ItemDim2Code)))
		b.WriteString(fmt.Sprintf("<td>%s</td>", htmlEsc(row.OldDueDate)))
		b.WriteString(fmt.Sprintf(`<td style="color:red;font-weight:bold;">%s</td>`, htmlEsc(row.NewDueDate)))
		b.WriteString("</tr>")
	}
	b.WriteString(`</table>`)
	return b.String()
}
