package controller

import (
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"

	"github.com/go-pdf/fpdf"
	"github.com/gofiber/fiber/v2"
	"gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto"
)

// ---------------------------------------------------------------------------
// Trigger
// ---------------------------------------------------------------------------

func isMarketingPdfExportRequest(c *fiber.Ctx) bool {
	return strings.EqualFold(strings.TrimSpace(c.Query("export")), "pdf")
}

// ---------------------------------------------------------------------------
// HTTP handler
// ---------------------------------------------------------------------------

func exportMarketingReportPdf(c *fiber.Ctx, items []dto.RepportMarketingItemDTO, query marketingPdfQueryMeta) error {
	content, err := buildMarketingReportPdf(items, query)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to generate pdf file")
	}

	filename := fmt.Sprintf("laporan_marketing_harian_%s.pdf", time.Now().Format("20060102_150405"))
	c.Set("Content-Type", "application/pdf")
	c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
	return c.Status(fiber.StatusOK).Send(content)
}

// marketingPdfQueryMeta holds display metadata for the PDF header.
type marketingPdfQueryMeta struct {
	StartDate string // e.g. "01 April 2026"
	EndDate   string // e.g. "29 April 2026"
	PrintedAt time.Time
}

// ---------------------------------------------------------------------------
// Column definitions
// ---------------------------------------------------------------------------

type pdfColumn struct {
	header string
	width  float64 // mm
	align  string  // "L", "C", "R"
}

var marketingPdfColumns = []pdfColumn{
	{"No", 6, "C"},
	{"Tanggal Sales Order", 16, "C"},
	{"Tanggal Delivery Order", 16, "C"},
	{"Aging\n(Hari)", 9, "C"},
	{"Gudang Fisik", 20, "L"},
	{"Pelanggan", 20, "L"},
	{"Sales", 18, "L"},
	{"Produk", 16, "L"},
	{"Nomor DO", 14, "C"},
	{"Nomor Polisi", 14, "C"},
	{"Tipe\nMarketing", 14, "C"},
	{"Quantity", 13, "R"},
	{"Rata-Rata\n(Kg)", 13, "R"},
	{"Total Berat\n(Kg)", 14, "R"},
	{"Harga Jual\n(Rp)", 17, "R"},
	{"HPP\n(Rp)", 17, "R"},
	{"Total Jual\n(Rp)", 18, "R"},
	{"Total HPP\n(Rp)", 18, "R"},
}

// ---------------------------------------------------------------------------
// Colours
// ---------------------------------------------------------------------------

const (
	headerR, headerG, headerB     = 30, 64, 120   // dark blue header bg
	headerTextR, headerTextG, headerTextB = 255, 255, 255 // white header text
	rowAltR, rowAltG, rowAltB     = 245, 247, 250  // alternating row bg
	borderR, borderG, borderB     = 200, 200, 200  // light border

	badgeTelurR, badgeTelurG, badgeTelurB     = 59, 130, 246  // blue
	badgeAyamR, badgeAyamG, badgeAyamB        = 34, 197, 94   // green
	badgeTradingR, badgeTradingG, badgeTradingB = 249, 115, 22 // orange
	badgeDefaultR, badgeDefaultG, badgeDefaultB = 107, 114, 128 // gray
)

// ---------------------------------------------------------------------------
// Workbook builder
// ---------------------------------------------------------------------------

func buildMarketingReportPdf(items []dto.RepportMarketingItemDTO, meta marketingPdfQueryMeta) ([]byte, error) {
	pdf := fpdf.New("L", "mm", "A4", "")
	pdf.SetMargins(10, 12, 10)
	pdf.SetAutoPageBreak(true, 12)
	pdf.AddPage()

	// ---- title ----
	pdf.SetFont("Helvetica", "B", 14)
	pdf.SetTextColor(30, 64, 120)
	pdf.CellFormat(0, 8, "Laporan > Penjualan Harian", "", 1, "L", false, 0, "")

	// ---- subtitle ----
	pdf.SetFont("Helvetica", "", 8)
	pdf.SetTextColor(80, 80, 80)
	dateLabel := buildMarketingPdfDateLabel(meta)
	printedAt := formatMarketingPdfDateTime(meta.PrintedAt)
	pdf.CellFormat(0, 5, fmt.Sprintf("%s     Dicetak: %s", dateLabel, printedAt), "", 1, "L", false, 0, "")
	pdf.Ln(3)

	// ---- table ----
	writeMarketingPdfHeader(pdf)
	writeMarketingPdfRows(pdf, items)
	writeMarketingPdfTotal(pdf, items)

	return marshalMarketingPdf(pdf)
}

func marshalMarketingPdf(pdf *fpdf.Fpdf) ([]byte, error) {
	w := &pdfByteBuffer{}
	err := pdf.Output(w)
	if err != nil {
		return nil, err
	}
	return w.Bytes(), nil
}

// pdfByteBuffer implements io.Writer and accumulates bytes.
type pdfByteBuffer struct {
	buf []byte
}

func (b *pdfByteBuffer) Write(p []byte) (n int, err error) {
	b.buf = append(b.buf, p...)
	return len(p), nil
}

func (b *pdfByteBuffer) Bytes() []byte { return b.buf }

// ---------------------------------------------------------------------------
// Header row
// ---------------------------------------------------------------------------

func writeMarketingPdfHeader(pdf *fpdf.Fpdf) {
	pdf.SetFont("Helvetica", "B", 6.5)
	pdf.SetFillColor(headerR, headerG, headerB)
	pdf.SetTextColor(headerTextR, headerTextG, headerTextB)
	pdf.SetDrawColor(borderR, borderG, borderB)
	pdf.SetLineWidth(0.1)

	headerH := 9.0 // height of header row (mm)

	x0 := pdf.GetX()
	y0 := pdf.GetY()

	for _, col := range marketingPdfColumns {
		lines := strings.Split(col.header, "\n")
		lineH := headerH / float64(len(lines))

		x := pdf.GetX()
		for li, line := range lines {
			pdf.SetXY(x, y0+float64(li)*lineH)
			pdf.CellFormat(col.width, lineH, line, "", 0, "C", true, 0, "")
		}
		pdf.SetXY(x+col.width, y0)
	}

	_ = x0
	pdf.SetXY(10, y0+headerH)
	pdf.Ln(0)
}

// ---------------------------------------------------------------------------
// Data rows
// ---------------------------------------------------------------------------

func writeMarketingPdfRows(pdf *fpdf.Fpdf, items []dto.RepportMarketingItemDTO) {
	pdf.SetFont("Helvetica", "", 6)
	pdf.SetDrawColor(borderR, borderG, borderB)
	pdf.SetLineWidth(0.1)

	rowH := 6.0

	for idx, item := range items {
		// page break check
		if pdf.GetY()+rowH > marketingPdfPageHeight(pdf)-12 {
			pdf.AddPage()
			writeMarketingPdfHeader(pdf)
			pdf.SetFont("Helvetica", "", 6)
		}

		// alternating bg
		if idx%2 == 1 {
			pdf.SetFillColor(rowAltR, rowAltG, rowAltB)
		} else {
			pdf.SetFillColor(255, 255, 255)
		}
		pdf.SetTextColor(40, 40, 40)

		y := pdf.GetY()
		writeMarketingPdfRow(pdf, idx+1, item, rowH, y)
	}
}

func writeMarketingPdfRow(pdf *fpdf.Fpdf, no int, item dto.RepportMarketingItemDTO, h, y float64) {
	fill := true // use the fill colour already set

	cols := marketingPdfColumns
	x := 10.0 // left margin

	values := marketingPdfRowValues(no, item)

	for i, col := range cols {
		pdf.SetXY(x, y)

		if i == 10 { // Tipe Marketing → badge
			drawMarketingTypeBadge(pdf, x, y, col.width, h, item.MarketingType)
		} else {
			pdf.CellFormat(col.width, h, values[i], "1", 0, col.align, fill, 0, "")
		}

		x += col.width
	}

	pdf.SetXY(10, y+h)
}

func marketingPdfRowValues(no int, item dto.RepportMarketingItemDTO) []string {
	warehouse := "-"
	if item.Warehouse != nil {
		warehouse = safeMarketingExportText(item.Warehouse.Name)
	}
	customer := "-"
	if item.Customer != nil {
		customer = safeMarketingExportText(item.Customer.Name)
	}
	sales := "-"
	if item.Sales != nil {
		sales = safeMarketingExportText(item.Sales.Name)
	}
	product := "-"
	if item.Product != nil {
		product = safeMarketingExportText(item.Product.Name)
	}

	return []string{
		strconv.Itoa(no),
		formatMarketingDate(item.SoDate),
		formatMarketingDate(item.RealizationDate),
		strconv.Itoa(item.AgingDays),
		warehouse,
		customer,
		sales,
		product,
		safeMarketingExportText(item.DoNumber),
		safeMarketingExportText(item.VehicleNumber),
		safeMarketingExportText(item.MarketingType), // index 10, overridden by badge
		formatMarketingPdfNumber(item.Qty),
		formatMarketingPdfDecimal(item.AverageWeightKg),
		formatMarketingPdfDecimal(item.TotalWeightKg),
		formatMarketingPdfRupiah(item.SalesPricePerKg),
		formatMarketingPdfRupiah(item.HppPricePerKg),
		formatMarketingPdfRupiah(item.SalesAmount),
		formatMarketingPdfRupiah(item.HppAmount),
	}
}

// ---------------------------------------------------------------------------
// Total row
// ---------------------------------------------------------------------------

func writeMarketingPdfTotal(pdf *fpdf.Fpdf, items []dto.RepportMarketingItemDTO) {
	summary := dto.ToSummaryFromDTOItems(items)
	if summary == nil {
		return
	}

	rowH := 6.5

	if pdf.GetY()+rowH > marketingPdfPageHeight(pdf)-12 {
		pdf.AddPage()
		writeMarketingPdfHeader(pdf)
	}

	pdf.SetFont("Helvetica", "B", 6)
	pdf.SetFillColor(220, 230, 245)
	pdf.SetTextColor(30, 64, 120)
	pdf.SetDrawColor(borderR, borderG, borderB)
	pdf.SetLineWidth(0.1)

	y := pdf.GetY()
	x := 10.0

	// merge first 11 cols (No … Tipe Marketing) into "TOTAL" label
	mergedWidth := 0.0
	for i := 0; i < 11; i++ {
		mergedWidth += marketingPdfColumns[i].width
	}
	pdf.SetXY(x, y)
	pdf.CellFormat(mergedWidth, rowH, "TOTAL", "1", 0, "R", true, 0, "")
	x += mergedWidth

	totals := []string{
		formatMarketingPdfNumber(float64(summary.TotalQty)),
		formatMarketingPdfDecimal(summary.AverageWeightKg),
		formatMarketingPdfDecimal(summary.TotalWeightKg),
		formatMarketingPdfRupiah(summary.AverageSalesPrice),
		formatMarketingPdfRupiah(summary.TotalHppPricePerKg),
		formatMarketingPdfRupiah(float64(summary.TotalSalesAmount)),
		formatMarketingPdfRupiah(float64(summary.TotalHppAmount)),
	}

	for i, val := range totals {
		col := marketingPdfColumns[11+i]
		pdf.SetXY(x, y)
		pdf.CellFormat(col.width, rowH, val, "1", 0, "R", true, 0, "")
		x += col.width
	}

	pdf.SetXY(10, y+rowH)
}

// ---------------------------------------------------------------------------
// Badge drawer
// ---------------------------------------------------------------------------

func drawMarketingTypeBadge(pdf *fpdf.Fpdf, x, y, w, h float64, marketingType string) {
	lower := strings.ToLower(strings.TrimSpace(marketingType))

	var r, g, b int
	switch lower {
	case "telur":
		r, g, b = badgeTelurR, badgeTelurG, badgeTelurB
	case "ayam":
		r, g, b = badgeAyamR, badgeAyamG, badgeAyamB
	case "trading":
		r, g, b = badgeTradingR, badgeTradingG, badgeTradingB
	default:
		r, g, b = badgeDefaultR, badgeDefaultG, badgeDefaultB
	}

	// badge background (slightly inset from the cell border)
	padH := 1.2
	padV := 1.2
	bx := x + padH
	by := y + padV
	bw := w - padH*2
	bh := h - padV*2

	pdf.SetFillColor(r, g, b)
	pdf.SetDrawColor(r, g, b)
	pdf.RoundedRect(bx, by, bw, bh, 1.5, "1234", "FD")

	// border of the cell itself (transparent bg, just border)
	pdf.SetDrawColor(borderR, borderG, borderB)
	pdf.SetFillColor(255, 255, 255)
	pdf.SetXY(x, y)
	pdf.CellFormat(w, h, "", "1", 0, "C", false, 0, "")

	// badge label
	pdf.SetFont("Helvetica", "B", 5.5)
	pdf.SetTextColor(255, 255, 255)
	label := strings.Title(strings.ToLower(marketingType))
	if label == "" {
		label = "-"
	}
	textW := pdf.GetStringWidth(label)
	pdf.SetXY(bx+(bw-textW)/2, by+(bh-3)/2)
	pdf.CellFormat(textW, 3, label, "", 0, "C", false, 0, "")

	// reset
	pdf.SetFont("Helvetica", "", 6)
	pdf.SetTextColor(40, 40, 40)
	pdf.SetDrawColor(borderR, borderG, borderB)
}

// ---------------------------------------------------------------------------
// Helpers — date/time
// ---------------------------------------------------------------------------

// buildMarketingPdfMeta converts raw query string dates into display meta.
func buildMarketingPdfMeta(startDate, endDate string) marketingPdfQueryMeta {
	loc, _ := time.LoadLocation("Asia/Jakarta")

	format := func(s string) string {
		t, err := time.ParseInLocation("2006-01-02", s, loc)
		if err != nil {
			return s
		}
		return t.Format("02 January 2006")
	}

	return marketingPdfQueryMeta{
		StartDate: format(startDate),
		EndDate:   format(endDate),
		PrintedAt: time.Now(),
	}
}

func buildMarketingPdfDateLabel(meta marketingPdfQueryMeta) string {
	if meta.StartDate != "" && meta.EndDate != "" {
		return fmt.Sprintf("Tanggal: %s - %s", meta.StartDate, meta.EndDate)
	}
	if meta.StartDate != "" {
		return fmt.Sprintf("Tanggal: %s", meta.StartDate)
	}
	if meta.EndDate != "" {
		return fmt.Sprintf("Tanggal: %s", meta.EndDate)
	}
	return fmt.Sprintf("Tanggal: %s", time.Now().Format("02 January 2006"))
}

func formatMarketingPdfDateTime(t time.Time) string {
	if t.IsZero() {
		t = time.Now()
	}
	loc, err := time.LoadLocation("Asia/Jakarta")
	if err == nil {
		t = t.In(loc)
	}
	return t.Format("02 Jan 2006 15:04")
}

// ---------------------------------------------------------------------------
// Helpers — number formatting
// ---------------------------------------------------------------------------

// formatMarketingPdfNumber formats a float64 as an integer with period-thousands separator.
// e.g. 7299 → "7.299"
func formatMarketingPdfNumber(v float64) string {
	return formatMarketingPdfThousands(int64(math.Round(v)))
}

// formatMarketingPdfDecimal formats a float64 with 2 decimal places (Indonesian locale).
// e.g. 452.54 → "452,54"
func formatMarketingPdfDecimal(v float64) string {
	rounded := math.Round(v*100) / 100
	intPart := int64(rounded)
	fracPart := int64(math.Round((rounded - float64(intPart)) * 100))
	if fracPart < 0 {
		fracPart = -fracPart
	}
	return fmt.Sprintf("%s,%02d", formatMarketingPdfThousands(intPart), fracPart)
}

// formatMarketingPdfRupiah formats a float64 as Rupiah with Indonesian locale.
// Drops trailing ",00" decimals for whole numbers.
// e.g. 25500 → "Rp 25.500"  |  240896.94 → "Rp 240.896,94"
func formatMarketingPdfRupiah(v float64) string {
	rounded := math.Round(v*100) / 100
	intPart := int64(rounded)
	fracPart := int64(math.Round((rounded - float64(intPart)) * 100))
	if fracPart < 0 {
		fracPart = -fracPart
	}

	negative := intPart < 0
	absInt := intPart
	if negative {
		absInt = -intPart
	}

	s := formatMarketingPdfThousands(absInt)
	var result string
	if fracPart == 0 {
		result = "Rp " + s
	} else {
		result = fmt.Sprintf("Rp %s,%02d", s, fracPart)
	}
	if negative {
		return "Rp -" + s
	}
	return result
}

// marketingPdfPageHeight returns the page height in mm.
func marketingPdfPageHeight(pdf *fpdf.Fpdf) float64 {
	_, h := pdf.GetPageSize()
	return h
}

// formatMarketingPdfThousands inserts period every 3 digits.
func formatMarketingPdfThousands(v int64) string {
	negative := v < 0
	abs := v
	if negative {
		abs = -v
	}

	numStr := strconv.FormatInt(abs, 10)
	n := len(numStr)

	var b strings.Builder
	for i, c := range numStr {
		if i > 0 && (n-i)%3 == 0 {
			b.WriteByte('.')
		}
		b.WriteRune(c)
	}

	if negative {
		return "-" + b.String()
	}
	return b.String()
}
