package report

import (
	"bytes"
	"context"
	"database/sql"
	"fmt"
	"log/slog"
	"reflect"
	"strconv"
	"time"
	"unicode/utf8"
	"wnzl-snow/db"

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

func CreateInventoryReport(logger *slog.Logger, Database db.Database, ctx context.Context) ([]byte, error) {
	//var xlsx *excelize.File
	sheetName := "Inventory Report"
	var buffer bytes.Buffer
	var cell string

	logger.Debug("Querying inventory table")
	results, err := Database.Queries().GetReportInventory(ctx)
	if err != nil {
		logger.Error("Unable to query inventory table", "error", err)
		return nil, err
	}

	if len(results) == 0 {
		logger.Error("Empty inventory results")
		return nil, fmt.Errorf("Empty inventory results")
	}

	// Create excel workbook
	xlsx := excelize.NewFile()
	err = xlsx.SetSheetName("Sheet1", sheetName)
	if err != nil {
		logger.Error("Error setting sheet name", "error", err, "sheet_name", sheetName)
		return nil, err
	}

	// Set the document properties
	err = xlsx.SetDocProps(&excelize.DocProperties{
		Creator: "json2excel",
		Created: time.Now().Format(time.RFC3339),
	})
	if err != nil {
		logger.Error("Error setting document properties", "error", err, "sheet_name", sheetName)
	}

	// Use reflection to determine column headings from the first item
	firstItem := results[0]
	v := reflect.ValueOf(firstItem)
	typeOfItem := v.Type()

	// Create column headers dynamically
	for i := 0; i < v.NumField(); i++ {
		column := string(rune('A'+i)) + "1" // A1, B1, C1, etc.
		xlsx.SetCellValue(sheetName, column, typeOfItem.Field(i).Name)
	}

	// Set autofilter on heading row
	cell, _ = excelize.CoordinatesToCellName(v.NumField(), 1)
	filterRange := "A1:" + cell
	logger.Debug("Setting autofilter", "range", filterRange)
	// As per docs any filters applied need to be manually processed by us (eg hiding rows with blanks)
	err = xlsx.AutoFilter(sheetName, filterRange, nil)
	if err != nil {
		logger.Error("Error setting autofilter", "error", err)
	}

	// Bold top row
	headerStyle, err := xlsx.NewStyle(&excelize.Style{
		Font: &excelize.Font{
			Bold: true,
		},
	})
	if err != nil {
		logger.Error("Error generating header style", "error", err)
	} else {
		err = xlsx.SetRowStyle(sheetName, 1, 1, headerStyle)
		if err != nil {
			logger.Error("Error setting header style", "error", err)
		}
	}

	// Populate the Excel file with data from the Inventory table
	for i, item := range results {
		v = reflect.ValueOf(item)
		for j := 0; j < v.NumField(); j++ {
			column := string(rune('A'+j)) + strconv.Itoa(i+2) // Start from row 2
			value := getFieldValue(v.Field(j))
			xlsx.SetCellValue(sheetName, column, value)
		}
	}

	// Freeze top row
	err = xlsx.SetPanes(sheetName, &excelize.Panes{
		Freeze:      true,
		Split:       false,
		XSplit:      0,
		YSplit:      1,
		TopLeftCell: "A2",
		ActivePane:  "bottomLeft",
		Selection: []excelize.Selection{
			{SQRef: "A2", ActiveCell: "A2", Pane: "bottomLeft"},
		},
	})
	if err != nil {
		logger.Error("Error freezing top row", "error", err)
	}

	// Set column autowidth
	/*
		err = SetColAutoWidth(xlsx, sheetName)
		if err != nil {
			fmt.Printf("Error setting auto width : '%s'\n", err)
		}
	*/

	// Save the Excel file into a byte buffer
	if err := xlsx.Write(&buffer); err != nil {
		return nil, err
	}

	return buffer.Bytes(), nil
}

func CreateUpdatesReport(logger *slog.Logger, Database db.Database, ctx context.Context) ([]byte, error) {
	//var xlsx *excelize.File
	sheetName := "Updates Report"
	var buffer bytes.Buffer
	var cell string

	logger.Debug("Querying updates table")
	results, err := Database.Queries().GetReportUpdates(ctx)
	if err != nil {
		logger.Error("Unable to query updates table", "error", err)
		return nil, err
	}

	if len(results) == 0 {
		logger.Error("Empty updates results")
		return nil, fmt.Errorf("Empty updates results")
	}

	// Create excel workbook
	xlsx := excelize.NewFile()
	err = xlsx.SetSheetName("Sheet1", sheetName)
	if err != nil {
		logger.Error("Error setting sheet name", "error", err, "sheet_name", sheetName)
		return nil, err
	}

	// Set the document properties
	err = xlsx.SetDocProps(&excelize.DocProperties{
		Creator: "json2excel",
		Created: time.Now().Format(time.RFC3339),
	})
	if err != nil {
		logger.Error("Error setting document properties", "error", err, "sheet_name", sheetName)
	}

	// Use reflection to determine column headings from the first item
	firstItem := results[0]
	v := reflect.ValueOf(firstItem)
	typeOfItem := v.Type()

	// Create column headers dynamically
	for i := 0; i < v.NumField(); i++ {
		column := string(rune('A'+i)) + "1" // A1, B1, C1, etc.
		xlsx.SetCellValue(sheetName, column, typeOfItem.Field(i).Name)
	}

	// Set autofilter on heading row
	cell, _ = excelize.CoordinatesToCellName(v.NumField(), 1)
	filterRange := "A1:" + cell
	logger.Debug("Setting autofilter", "range", filterRange)
	// As per docs any filters applied need to be manually processed by us (eg hiding rows with blanks)
	err = xlsx.AutoFilter(sheetName, filterRange, nil)
	if err != nil {
		logger.Error("Error setting autofilter", "error", err)
	}

	// Bold top row
	headerStyle, err := xlsx.NewStyle(&excelize.Style{
		Font: &excelize.Font{
			Bold: true,
		},
	})
	if err != nil {
		logger.Error("Error generating header style", "error", err)
	} else {
		err = xlsx.SetRowStyle(sheetName, 1, 1, headerStyle)
		if err != nil {
			logger.Error("Error setting header style", "error", err)
		}
	}

	// Populate the Excel file with data from the Inventory table
	for i, item := range results {
		v = reflect.ValueOf(item)
		for j := 0; j < v.NumField(); j++ {
			column := string(rune('A'+j)) + strconv.Itoa(i+2) // Start from row 2
			value := getFieldValue(v.Field(j))
			xlsx.SetCellValue(sheetName, column, value)
		}
	}

	// Freeze top row
	err = xlsx.SetPanes(sheetName, &excelize.Panes{
		Freeze:      true,
		Split:       false,
		XSplit:      0,
		YSplit:      1,
		TopLeftCell: "A2",
		ActivePane:  "bottomLeft",
		Selection: []excelize.Selection{
			{SQRef: "A2", ActiveCell: "A2", Pane: "bottomLeft"},
		},
	})
	if err != nil {
		logger.Error("Error freezing top row", "error", err)
	}

	// Set column autowidth
	/*
		err = SetColAutoWidth(xlsx, sheetName)
		if err != nil {
			fmt.Printf("Error setting auto width : '%s'\n", err)
		}
	*/

	// Save the Excel file into a byte buffer
	if err := xlsx.Write(&buffer); err != nil {
		return nil, err
	}

	return buffer.Bytes(), nil
}

// Helper function to get the actual value of sql.Null types
func getFieldValue(field reflect.Value) interface{} {
	switch field.Kind() {
	case reflect.Struct:
		// Handle sql.Null types based on their concrete type
		switch field.Interface().(type) {
		case sql.NullString:
			ns := field.Interface().(sql.NullString)
			if ns.Valid {
				return ns.String
			}
			return ""
		case sql.NullInt64:
			ni := field.Interface().(sql.NullInt64)
			if ni.Valid {
				return ni.Int64
			}
			return -1
		case sql.NullFloat64:
			nf := field.Interface().(sql.NullFloat64)
			if nf.Valid {
				return nf.Float64
			}
			return nil
		case sql.NullBool:
			nb := field.Interface().(sql.NullBool)
			if nb.Valid {
				return nb.Bool
			}
			return false
		}
	}
	return field.Interface() // Return the value as-is for non-sql.Null types
}

// Taken from https://github.com/qax-os/excelize/issues/92#issuecomment-821578446
func SetColAutoWidth(xlsx *excelize.File, sheetName string) error {
	// Autofit all columns according to their text content
	cols, err := xlsx.GetCols(sheetName)
	if err != nil {
		return err
	}
	for idx, col := range cols {
		largestWidth := 0
		for _, rowCell := range col {
			cellWidth := utf8.RuneCountInString(rowCell) + 2 // + 2 for margin
			if cellWidth > largestWidth {
				largestWidth = cellWidth
			}
		}
		//fmt.Printf("SetColAutoWidth calculated largest width for column index '%d' is '%d'\n", idx, largestWidth)
		name, err := excelize.ColumnNumberToName(idx + 1)
		if err != nil {
			return err
		}
		xlsx.SetColWidth(sheetName, name, name, float64(largestWidth))
	}
	// No errors at this point
	return nil
}
