package templates

import (
	"bytes"
	"fmt"
	"strings"

	. "primesys_email/src/models/report"
	"primesys_email/src/templates/utils"

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

// tableReport is the common layout of the device reports: title + date, meta row,
// spacer, header row and one row per device with alternating background.
type tableReport struct {
	sheet        string
	title        string
	widths       []float64
	headers      []string
	headerHeight float64
}

// tableStyles are the style ids a row can use
type tableStyles struct {
	name int // bold teal, left aligned
	cell int // normal centered
	yes  int // red, e.g. overspeed / delay
	no   int // green
}

type tableCell struct {
	value string
	style int
}

// writeTableReport builds the workbook. row returns the cells of report i, the title is merged
// over all columns except the last two, which hold the date.
func writeTableReport(t tableReport, reports []DivisionTripReportModel, divisionName string, reportDate string,
	row func(r DivisionTripReportModel, s tableStyles) []tableCell) ([]byte, error) {

	f := excelize.NewFile()
	defer func() {
		_ = f.Close()
	}()

	// excel allows at most 31 characters, POI truncated longer names the same way
	sheet := t.sheet
	if len(sheet) > 31 {
		sheet = sheet[:31]
	}
	if err := f.SetSheetName(f.GetSheetName(0), sheet); err != nil {
		return nil, err
	}

	for i, width := range t.widths {
		col, err := excelize.ColumnNumberToName(i + 1)
		if err != nil {
			return nil, err
		}
		if err := f.SetColWidth(sheet, col, col, width); err != nil {
			return nil, err
		}
	}

	oddStyles, err := mateDataStyles(f, "FFFFFF")
	if err != nil {
		return nil, err
	}

	evenStyles, err := mateDataStyles(f, "F4FAFA")
	if err != nil {
		return nil, err
	}

	yesStyle := yesNoStyle(f, "791F1F", "FCEBEB")
	noStyle := yesNoStyle(f, "085041", "E1F5EE")

	lastCol := len(t.headers)

	// Title row.
	if err := f.SetRowHeight(sheet, 1, 22); err != nil {
		return nil, err
	}
	setTableCell(f, sheet, 1, 1, t.title, utils.TitleStyle(f))
	_ = f.MergeCell(sheet, cellName(1, 1), cellName(lastCol-2, 1))
	setTableCell(f, sheet, lastCol-1, 1, reportDate, utils.DateStyle(f))
	_ = f.MergeCell(sheet, cellName(lastCol-1, 1), cellName(lastCol, 1))

	// Metadata row.
	if err := f.SetRowHeight(sheet, 2, 16); err != nil {
		return nil, err
	}
	metaLabel, metaValue := utils.MetaLabelStyle(f), utils.MetaValueStyle(f)
	setMateMetaPair(f, sheet, "A2", "B2", "Division: ", divisionName+"    ", metaLabel, metaValue)
	setMateMetaPair(f, sheet, "C2", "D2", "Date: ", reportDate+"    ", metaLabel, metaValue)
	setMateMetaPair(f, sheet, "E2", "F2", "Total Devices: ", fmt.Sprint(len(reports)), metaLabel, metaValue)

	// Row 3 is the spacer, header on row 4.
	headerHeight := t.headerHeight
	if headerHeight == 0 {
		headerHeight = 18
	}
	if err := f.SetRowHeight(sheet, 4, headerHeight); err != nil {
		return nil, err
	}
	headerStyle := utils.HeaderStyle(f)
	for i, header := range t.headers {
		setTableCell(f, sheet, i+1, 4, header, headerStyle)
	}

	// Data rows start at row 5, Sr.No is always the first column.
	for i, r := range reports {
		rowNum := i + 5
		if err := f.SetRowHeight(sheet, rowNum, 16); err != nil {
			return nil, err
		}

		styles := oddStyles
		if i%2 != 0 {
			styles = evenStyles
		}
		s := tableStyles{name: styles[0], cell: styles[1], yes: yesStyle, no: noStyle}

		setTableCell(f, sheet, 1, rowNum, fmt.Sprint(i+1), s.cell)
		for col, c := range row(r, s) {
			setTableCell(f, sheet, col+2, rowNum, c.value, c.style)
		}
	}

	var buf bytes.Buffer
	if err := f.Write(&buf); err != nil {
		return nil, fmt.Errorf("failed to generate %s Excel: %w", t.sheet, err)
	}

	return buf.Bytes(), nil
}

func yesNoStyle(f *excelize.File, color string, background string) int {
	return utils.CreateStyle(f, map[string]any{
		"font":      &excelize.Font{Family: "Arial", Size: 8, Bold: true, Color: color},
		"fill":      excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{background}},
		"alignment": &excelize.Alignment{Horizontal: "center", Vertical: "center"},
		"border":    utils.ThinBorders("DDDDEE"),
	})
}

func cellName(col, row int) string {
	name, _ := excelize.CoordinatesToCellName(col, row)
	return name
}

func setTableCell(f *excelize.File, sheet string, col, row int, value string, style int) {
	cell := cellName(col, row)
	_ = f.SetCellValue(sheet, cell, value)
	_ = f.SetCellStyle(sheet, cell, cell, style)
}

// -----------------------------------------------------------------------------
// Value helpers shared by the reports
// -----------------------------------------------------------------------------

// timeText is HH:mm in IST, "-" when missing
func timeText(epoch *int64) string {
	if value := utils.DefaultTime(epoch); value != "" {
		return value
	}
	return "-"
}

func timeRange(start, end *int64) string {
	return timeText(start) + " - " + timeText(end)
}

// floatText formats with the given decimals, fallback when missing
func floatText(value *float64, decimals int, fallback string) string {
	if value == nil {
		return fallback
	}
	return fmt.Sprintf("%.*f", decimals, *value)
}

func kmRange(start, end *float64, decimals int) string {
	return floatText(start, decimals, "-") + " - " + floatText(end, decimals, "-")
}

func intText(value *int32, fallback string) string {
	if value == nil {
		return fallback
	}
	return fmt.Sprint(*value)
}

// batteryText is the first trip's start battery and the last trip's start battery
func batteryText(trips []TripReportStatusModel, empty string) string {
	if len(trips) == 0 {
		return empty
	}

	start := utils.BatteryPercent(trips[0].DeviceTripStartBatteryStatus)
	end := utils.BatteryPercent(trips[len(trips)-1].DeviceTripStartBatteryStatus)

	return start + "% - " + end + "%"
}

// remarkText hides "All work done successfully"
func remarkText(remark *string) string {
	if remark != nil && strings.EqualFold(*remark, "All work done successfully") {
		return "-"
	}
	return utils.DefaultString(remark)
}

// totalWorking adds up the actual duration of every trip
func totalWorking(trips []TripReportStatusModel) string {
	var total int64

	for _, trip := range trips {
		if trip.TripActualStartTime == nil || trip.TripActualEndTime == nil {
			continue
		}
		total += *trip.TripActualEndTime - *trip.TripActualStartTime
	}

	return hoursMinutes(total)
}

// totalIdle adds up every stoppage of every trip
func totalIdle(trips []TripReportStatusModel) string {
	var idle int64

	for _, trip := range trips {
		for _, stoppage := range trip.StoppageStatuses {
			if stoppage.StoppageStartTime == nil || stoppage.StoppageEndTime == nil {
				continue
			}
			idle += *stoppage.StoppageEndTime - *stoppage.StoppageStartTime
		}
	}

	return hoursMinutes(idle)
}

// hoursMinutes formats seconds as "05 h:07 m", "-" when zero
func hoursMinutes(seconds int64) string {
	if seconds == 0 {
		return "-"
	}
	return fmt.Sprintf("%02d h:%02d m", seconds/3600, (seconds%3600)/60)
}

// yesNoCell is red "Yes", green "No" or neutral "-"
func yesNoCell(value string, s tableStyles) tableCell {
	switch value {
	case "Yes":
		return tableCell{value, s.yes}
	case "No":
		return tableCell{value, s.no}
	}
	return tableCell{"-", s.cell}
}
