package controller

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

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

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

const expenseReportExportSheetName = "Expense Reports"

var expenseReportTemplateSheetOrder = []string{
	"UANG MAKAN",
	"UPAH",
	"EKSPEDISI ADE",
	"GALON",
	"GAS",
	"KEBUTUHAN",
	"EKSPEDISI LTI",
	"KONTRIBUSI",
	"PRODUKSI",
	"KOMPENSASI",
	"LAIN-LAIN",
	"PERBAIKAN",
	"LISTRIK",
	"PAJAK",
	"SOLAR",
}

var expenseReportSheetAliasMap = map[string]string{
	"TRANSPORT 2":  "EKSPEDISI ADE",
	"TRANSPORT":    "EKSPEDISI LTI",
	"GAS BROODING": "GAS",
}

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

func exportExpenseReportListExcel(c *fiber.Ctx, items []dto.RepportExpenseListDTO) error {
	content, err := buildExpenseReportExportWorkbook(items)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to generate excel file")
	}

	filename := fmt.Sprintf("reports_expense_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 buildExpenseReportExportWorkbook(items []dto.RepportExpenseListDTO) ([]byte, error) {
	file := excelize.NewFile()
	defer file.Close()

	defaultSheet := file.GetSheetName(file.GetActiveSheetIndex())
	groups := groupExpenseReportRowsBySheet(items)
	orderedSheetNames := orderExpenseReportSheetNames(groups)

	if len(orderedSheetNames) == 0 {
		if defaultSheet != expenseReportExportSheetName {
			if err := file.SetSheetName(defaultSheet, expenseReportExportSheetName); err != nil {
				return nil, err
			}
		}
		if err := writeExpenseReportSheet(file, expenseReportExportSheetName, []dto.RepportExpenseListDTO{}); err != nil {
			return nil, err
		}
	} else {
		for idx, sheetName := range orderedSheetNames {
			if idx == 0 {
				if defaultSheet != sheetName {
					if err := file.SetSheetName(defaultSheet, sheetName); err != nil {
						return nil, err
					}
				}
			} else {
				if _, err := file.NewSheet(sheetName); err != nil {
					return nil, err
				}
			}

			if err := writeExpenseReportSheet(file, sheetName, groups[sheetName]); err != nil {
				return nil, err
			}
		}
	}

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

	return buffer.Bytes(), nil
}

func groupExpenseReportRowsBySheet(items []dto.RepportExpenseListDTO) map[string][]dto.RepportExpenseListDTO {
	groups := make(map[string][]dto.RepportExpenseListDTO)
	for _, item := range items {
		product := resolveExpenseReportProduct(item)
		sheetName := resolveExpenseReportSheetName(product)
		groups[sheetName] = append(groups[sheetName], item)
	}
	return groups
}

func orderExpenseReportSheetNames(groups map[string][]dto.RepportExpenseListDTO) []string {
	if len(groups) == 0 {
		return nil
	}

	templateSet := make(map[string]struct{}, len(expenseReportTemplateSheetOrder))
	ordered := make([]string, 0, len(groups))

	for _, sheet := range expenseReportTemplateSheetOrder {
		templateSet[sheet] = struct{}{}
		if _, ok := groups[sheet]; ok {
			ordered = append(ordered, sheet)
		}
	}

	extras := make([]string, 0)
	for sheet := range groups {
		if _, ok := templateSet[sheet]; !ok {
			extras = append(extras, sheet)
		}
	}

	sort.Slice(extras, func(i, j int) bool {
		return strings.ToUpper(extras[i]) < strings.ToUpper(extras[j])
	})

	ordered = append(ordered, extras...)
	return ordered
}

func resolveExpenseReportSheetName(product string) string {
	normalizedProduct := strings.ToUpper(strings.TrimSpace(product))
	if alias, exists := expenseReportSheetAliasMap[normalizedProduct]; exists {
		return alias
	}
	if normalizedProduct == "" {
		normalizedProduct = "-"
	}
	return sanitizeExpenseReportSheetName(normalizedProduct)
}

func sanitizeExpenseReportSheetName(name string) string {
	replacer := strings.NewReplacer(
		":", " ",
		"\\", " ",
		"/", " ",
		"?", " ",
		"*", " ",
		"[", " ",
		"]", " ",
	)

	sanitized := strings.TrimSpace(replacer.Replace(name))
	if sanitized == "" {
		sanitized = "Sheet"
	}

	runes := []rune(sanitized)
	if len(runes) > 31 {
		sanitized = string(runes[:31])
	}

	return sanitized
}

func writeExpenseReportSheet(file *excelize.File, sheet string, items []dto.RepportExpenseListDTO) error {
	if err := setExpenseReportTemplateColumns(file, sheet); err != nil {
		return err
	}
	if err := setExpenseReportTemplateHeaders(file, sheet); err != nil {
		return err
	}
	return setExpenseReportTemplateRows(file, sheet, items)
}

func setExpenseReportTemplateColumns(file *excelize.File, sheet string) error {
	columnWidths := map[string]float64{
		"A": 5.83203125,
		"B": 20.83203125,
		"C": 20.83203125,
		"D": 15.83203125,
		"E": 15.83203125,
		"F": 15.83203125,
		"G": 30.83203125,
		"H": 20.83203125,
		"I": 15.83203125,
		"J": 15.83203125,
		"K": 15.83203125,
		"L": 20.83203125,
		"M": 15.83203125,
		"N": 20.83203125,
	}

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

	return nil
}

func setExpenseReportTemplateHeaders(file *excelize.File, sheet string) error {
	headers := []string{
		"No",
		"No. PO",
		"No. Referensi",
		"Tanggal Realisasi",
		"Tanggal Transaksi",
		"Kategori",
		"Produk",
		"Lokasi",
		"Kandang",
		"Qty Pengajuan",
		"Harga Pengajuan",
		"Total Pengajuan",
		"Qty Realisasi",
		"Harga Realisasi",
		"Total Realisasi",
		"Status Pencairan",
	}

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

	return nil
}

func setExpenseReportTemplateRows(file *excelize.File, sheet string, items []dto.RepportExpenseListDTO) error {
	totalQtyPengajuan := 0.0
	totalPengajuan := 0.0
	totalQtyRealisasi := 0.0
	totalRealisasi := 0.0

	for idx, item := range items {
		row := idx + 2
		rowString := strconv.Itoa(row)

		produk := resolveExpenseReportProduct(item)
		status := formatExpenseReportStatus(item)
		lokasi := resolveExpenseReportLocation(item)
		kandang := resolveExpenseReportKandang(item)

		if err := file.SetCellValue(sheet, "A"+rowString, idx+1); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "B"+rowString, safeExpenseReportExportText(item.PoNumber)); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "C"+rowString, safeExpenseReportExportText(item.ReferenceNumber)); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "D"+rowString, formatExpenseReportOptionalDate(item.RealizationDate)); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "E"+rowString, formatExpenseReportDate(item.TransactionDate)); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "F"+rowString, safeExpenseReportExportText(item.Category)); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "G"+rowString, produk); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "H"+rowString, lokasi); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "I"+rowString, kandang); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "J"+rowString, item.Pengajuan.Qty); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "K"+rowString, item.Pengajuan.Price); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "L"+rowString, item.TotalPengajuan); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "M"+rowString, item.Realisasi.Qty); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "N"+rowString, item.Realisasi.Price); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "O"+rowString, item.TotalRealisasi); err != nil {
			return err
		}
		if err := file.SetCellValue(sheet, "P"+rowString, status); err != nil {
			return err
		}

		totalQtyPengajuan += item.Pengajuan.Qty
		totalPengajuan += item.TotalPengajuan
		totalQtyRealisasi += item.Realisasi.Qty
		totalRealisasi += item.TotalRealisasi
	}

	totalRow := strconv.Itoa(len(items) + 2)
	if err := file.SetCellValue(sheet, "A"+totalRow, "Total"); err != nil {
		return err
	}
	if err := file.SetCellValue(sheet, "J"+totalRow, totalQtyPengajuan); err != nil {
		return err
	}
	if err := file.SetCellValue(sheet, "K"+totalRow, 0); err != nil {
		return err
	}
	if err := file.SetCellValue(sheet, "L"+totalRow, totalPengajuan); err != nil {
		return err
	}
	if err := file.SetCellValue(sheet, "M"+totalRow, totalQtyRealisasi); err != nil {
		return err
	}
	if err := file.SetCellValue(sheet, "N"+totalRow, 0); err != nil {
		return err
	}
	if err := file.SetCellValue(sheet, "O"+totalRow, totalRealisasi); err != nil {
		return err
	}

	return nil
}

func resolveExpenseReportProduct(item dto.RepportExpenseListDTO) string {
	if item.Realisasi.Nonstock != nil {
		name := strings.TrimSpace(item.Realisasi.Nonstock.Name)
		if name != "" {
			return name
		}
	}
	if item.Pengajuan.Nonstock != nil {
		name := strings.TrimSpace(item.Pengajuan.Nonstock.Name)
		if name != "" {
			return name
		}
	}
	return "-"
}

func resolveExpenseReportLocation(item dto.RepportExpenseListDTO) string {
	if item.Kandang != nil && item.Kandang.Location != nil {
		name := strings.TrimSpace(item.Kandang.Location.Name)
		if name != "" {
			return name
		}
	}
	return "-"
}

func resolveExpenseReportKandang(item dto.RepportExpenseListDTO) string {
	if item.Kandang != nil {
		name := strings.TrimSpace(item.Kandang.Name)
		if name != "" {
			return name
		}
	}
	return "-"
}

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

	stepName := strings.TrimSpace(item.LatestApproval.StepName)
	if stepName == "" {
		return "-"
	}
	return stepName
}

func formatExpenseReportDate(value time.Time) string {
	if value.IsZero() {
		return "-"
	}

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

	return value.Format("02 Jan 2006")
}

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

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