package controller

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

	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	"gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/dto"

	"github.com/gofiber/fiber/v2"
	"github.com/xuri/excelize/v2"
)

const purchaseExportSheetName = "Purchases"

func isAllPurchaseExcelExportRequest(c *fiber.Ctx) bool {
	return strings.EqualFold(strings.TrimSpace(c.Query("export")), "excel") &&
		strings.EqualFold(strings.TrimSpace(c.Query("type")), "all")
}

func exportPurchaseListExcel(c *fiber.Ctx, purchases []entity.Purchase) error {
	content, err := buildPurchaseExportWorkbook(purchases)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to generate excel file")
	}

	filename := fmt.Sprintf("purchases_all_%s.xlsx", time.Now().Format("20060102_150405"))
	c.Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
	c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
	return c.Status(fiber.StatusOK).Send(content)
}

func buildPurchaseExportWorkbook(purchases []entity.Purchase) ([]byte, error) {
	file := excelize.NewFile()
	defer file.Close()

	defaultSheet := file.GetSheetName(file.GetActiveSheetIndex())
	if defaultSheet != purchaseExportSheetName {
		if err := file.SetSheetName(defaultSheet, purchaseExportSheetName); err != nil {
			return nil, err
		}
	}

	listItems := dto.ToPurchaseListDTOs(purchases)
	grandTotals := buildPurchaseGrandTotalMap(purchases)

	if err := setPurchaseExportColumns(file, purchaseExportSheetName); err != nil {
		return nil, err
	}
	if err := setPurchaseExportHeaders(file, purchaseExportSheetName); err != nil {
		return nil, err
	}
	if err := setPurchaseExportRows(file, purchaseExportSheetName, listItems, grandTotals); err != nil {
		return nil, err
	}
	if err := file.SetPanes(purchaseExportSheetName, &excelize.Panes{
		Freeze:      true,
		YSplit:      1,
		TopLeftCell: "A2",
		ActivePane:  "bottomLeft",
	}); err != nil {
		return nil, err
	}

	buffer, err := file.WriteToBuffer()
	if err != nil {
		return nil, err
	}
	return buffer.Bytes(), nil
}

func setPurchaseExportColumns(file *excelize.File, sheet string) error {
	columnWidths := map[string]float64{
		"A": 16,
		"B": 16,
		"C": 14,
		"D": 22,
		"E": 22,
		"F": 18,
		"G": 18,
		"H": 52,
		"I": 24,
	}

	for col, width := range columnWidths {
		if err := file.SetColWidth(sheet, col, col, width); err != nil {
			return err
		}
	}

	if err := file.SetRowHeight(sheet, 1, 24); err != nil {
		return err
	}
	return nil
}

func setPurchaseExportHeaders(file *excelize.File, sheet string) error {
	headers := []string{
		"PR Number",
		"PO Number",
		"Tanggal PO",
		"Supplier",
		"Lokasi",
		"Status",
		"Grand Total",
		"Products",
		"Notes",
	}

	for i, header := range headers {
		colName, err := excelize.ColumnNumberToName(i + 1)
		if err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, colName+"1", header); err != nil {
			return err
		}
	}

	headerStyle, err := file.NewStyle(&excelize.Style{
		Font:      &excelize.Font{Bold: true, Color: "1F2937"},
		Fill:      excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"DCEBFA"}},
		Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
		Border: []excelize.Border{
			{Type: "left", Color: "D1D5DB", Style: 1},
			{Type: "top", Color: "D1D5DB", Style: 1},
			{Type: "bottom", Color: "D1D5DB", Style: 1},
			{Type: "right", Color: "D1D5DB", Style: 1},
		},
	})
	if err != nil {
		return err
	}

	return file.SetCellStyle(sheet, "A1", "I1", headerStyle)
}

func setPurchaseExportRows(file *excelize.File, sheet string, items []dto.PurchaseListDTO, grandTotals map[uint]float64) error {
	if len(items) == 0 {
		return nil
	}

	for i, item := range items {
		row := strconv.Itoa(i + 2)
		if err := file.SetCellValue(sheet, "A"+row, safePurchaseExportText(item.PrNumber)); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "B"+row, safePurchaseExportPointerText(item.PoNumber)); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "C"+row, formatPurchaseExportDate(item.PoDate)); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "D"+row, safePurchaseSupplierName(item)); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "E"+row, safePurchaseLocationName(item)); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "F"+row, formatPurchaseExportStatus(item)); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "G"+row, formatPurchaseRupiah(grandTotals[item.Id])); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "H"+row, formatPurchaseProducts(item)); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "I"+row, safePurchaseExportPointerText(item.Notes)); err != nil {
			return err
		}
	}

	lastRow := len(items) + 1
	dataStyle, err := file.NewStyle(&excelize.Style{
		Alignment: &excelize.Alignment{
			Horizontal: "left",
			Vertical:   "center",
			WrapText:   true,
		},
		Border: []excelize.Border{
			{Type: "left", Color: "D1D5DB", Style: 1},
			{Type: "top", Color: "D1D5DB", Style: 1},
			{Type: "bottom", Color: "D1D5DB", Style: 1},
			{Type: "right", Color: "D1D5DB", Style: 1},
		},
	})
	if err != nil {
		return err
	}
	if err := file.SetCellStyle(sheet, "A2", "I"+strconv.Itoa(lastRow), dataStyle); err != nil {
		return err
	}

	moneyStyle, err := file.NewStyle(&excelize.Style{
		Alignment: &excelize.Alignment{
			Horizontal: "right",
			Vertical:   "center",
		},
		Border: []excelize.Border{
			{Type: "left", Color: "D1D5DB", Style: 1},
			{Type: "top", Color: "D1D5DB", Style: 1},
			{Type: "bottom", Color: "D1D5DB", Style: 1},
			{Type: "right", Color: "D1D5DB", Style: 1},
		},
	})
	if err != nil {
		return err
	}

	return file.SetCellStyle(sheet, "G2", "G"+strconv.Itoa(lastRow), moneyStyle)
}

func buildPurchaseGrandTotalMap(items []entity.Purchase) map[uint]float64 {
	result := make(map[uint]float64, len(items))
	for i := range items {
		total := 0.0
		for j := range items[i].Items {
			total += items[i].Items[j].TotalPrice
		}
		result[items[i].Id] = total
	}
	return result
}

func safePurchaseSupplierName(item dto.PurchaseListDTO) string {
	if item.Supplier == nil {
		return "-"
	}
	return safePurchaseExportText(item.Supplier.Name)
}

func safePurchaseLocationName(item dto.PurchaseListDTO) string {
	if item.Location == nil {
		return "-"
	}
	return safePurchaseExportText(item.Location.Name)
}

func formatPurchaseExportStatus(item dto.PurchaseListDTO) string {
	if item.LatestApproval == nil {
		return "-"
	}

	if item.LatestApproval.Action != nil &&
		strings.EqualFold(strings.TrimSpace(*item.LatestApproval.Action), string(entity.ApprovalActionRejected)) {
		return "Ditolak"
	}

	return safePurchaseExportText(item.LatestApproval.StepName)
}

func formatPurchaseExportDate(value *time.Time) string {
	if value == nil || value.IsZero() {
		return "-"
	}

	t := *value
	location, err := time.LoadLocation("Asia/Jakarta")
	if err == nil {
		t = t.In(location)
	}

	return t.Format("02-01-2006")
}

func formatPurchaseProducts(item dto.PurchaseListDTO) string {
	if len(item.Products) == 0 {
		return "-"
	}

	seen := make(map[string]struct{})
	names := make([]string, 0, len(item.Products))
	for i := range item.Products {
		name := strings.TrimSpace(item.Products[i].Name)
		if name == "" {
			continue
		}
		if _, exists := seen[name]; exists {
			continue
		}
		seen[name] = struct{}{}
		names = append(names, name)
	}

	if len(names) == 0 {
		return "-"
	}

	sort.Strings(names)
	return strings.Join(names, ", ")
}

func safePurchaseExportPointerText(value *string) string {
	if value == nil {
		return "-"
	}
	return safePurchaseExportText(*value)
}

func safePurchaseExportText(value string) string {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return "-"
	}
	return trimmed
}

func formatPurchaseRupiah(value float64) string {
	if math.IsNaN(value) || math.IsInf(value, 0) {
		return "Rp 0"
	}

	rounded := int64(math.Round(value))
	sign := ""
	if rounded < 0 {
		sign = "-"
		rounded = -rounded
	}

	raw := strconv.FormatInt(rounded, 10)
	if raw == "" {
		raw = "0"
	}

	var grouped strings.Builder
	rem := len(raw) % 3
	if rem > 0 {
		grouped.WriteString(raw[:rem])
		if len(raw) > rem {
			grouped.WriteString(".")
		}
	}
	for i := rem; i < len(raw); i += 3 {
		grouped.WriteString(raw[i : i+3])
		if i+3 < len(raw) {
			grouped.WriteString(".")
		}
	}

	return "Rp " + sign + grouped.String()
}
