package utils

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

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

// ParseDate tries multiple formats to parse a date string
func ParseDate(val string) (time.Time, error) {
	rawVal := strings.Trim(strings.TrimSpace(val), "'\"")
	if rawVal == "" || rawVal == "0000-00-00" || rawVal == "0000-00-00 00:00:00" || rawVal == "-" {
		return time.Time{}, errors.New("empty or invalid date string")
	}

	lowerVal := strings.ToLower(rawVal)
	// Fast Mapping for ID Shorthands using Replacer for performance
	replacer := strings.NewReplacer(
		"februari", "Feb", "pebruari", "Feb", "januari", "Jan", "agustus", "Aug",
		"oktober", "Oct", "nopember", "Nov", "desember", "Dec", "april", "Apr",
		"maret", "Mar", "junia", "Jun", "juli", "Jul", "jan", "Jan", "peb", "Feb",
		"mar", "Mar", "apr", "Apr", "mei", "May", "jun", "Jun", "jul", "Jul",
		"agu", "Aug", "agt", "Aug", "sep", "Sep", "okt", "Oct", "nop", "Nov", "des", "Dec",
	)
	normalizedVal := replacer.Replace(lowerVal)

	layouts := []string{
		"02/01/2006", "02-01-2006", "2006-01-02", "02/01/06",
		"2/1/2006", "2006.01.02", "02-Jan-2006", "Jan 02, 2006",
		"02.01.2006", "2/1/06", "02-01-06", "02 Jan 06", "01/02/2006",
		"02 Jan 2006", "2 Jan 2006", "2006/01/02", "02-Jan-06",
		"02-Jan-2006", "02-Feb-2006", "02-Mar-2006", "02-Apr-2006",
		"02-May-2006", "02-Jun-2006", "02-Jul-2006", "02-Aug-2006",
		"02-Sep-2006", "02-Oct-2006", "02-Nov-2006", "02-Dec-2006",
		"01/02/06", "2/1/2006", "2/1/06", "1/2/2006", "1/2/06",
		"02-01-02", "02/01/02", "02.01.02",
		time.RFC3339, time.RFC3339Nano, "2006-01-02 15:04:05", "2006/01/02 15:04:05",
	}

	tryLayouts := func(input string) (time.Time, bool) {
		for _, layout := range layouts {
			if t, err := time.Parse(layout, input); err == nil {
				return t, true
			}
		}

		return time.Time{}, false
	}

	for _, candidate := range []string{rawVal, normalizedVal} {
		if t, ok := tryLayouts(candidate); ok {
			return t, nil
		}

		altCandidate := strings.ReplaceAll(candidate, "/0", "/")
		altCandidate = strings.ReplaceAll(altCandidate, "-0", "-")
		if t, ok := tryLayouts(altCandidate); ok {
			return t, nil
		}
	}

	// Try with Title Case or Uppercase for named months and exported timestamps.
	titleVal := strings.Title(lowerVal)
	upperVal := strings.ToUpper(rawVal)
	for _, candidate := range []string{titleVal, upperVal} {
		if t, ok := tryLayouts(candidate); ok {
			return t, nil
		}
	}

	// Double handle dot separator: common in some exports (01.03.2026)
	dotClean := strings.ReplaceAll(rawVal, ".", "/")
	for _, l := range []string{"02/01/2006", "02/01/06", "01/02/2006", "01/02/06"} {
		if t, err := time.Parse(l, dotClean); err == nil {
			return t, nil
		}
	}

	// Handle Excel Serial Date (Numeric)
	if f, err := strconv.ParseFloat(rawVal, 64); err == nil && f > 20000 {
		return excelize.ExcelDateToTime(f, false)
	}

	return time.Time{}, fmt.Errorf("format tanggal tidak dikenali: %s", rawVal)
}

// ParseFloat handles currency prefix/suffix and various separators
func ParseFloat(val string) float64 {
	return parseSmartFloat(val)
}

func parseSmartFloat(val string) float64 {
	s := strings.TrimSpace(strings.ToLower(val))
	if s == "" || s == "-" || s == "v" || s == "x" {
		return 0
	}

	// Basic Clean
	s = strings.ReplaceAll(s, "rp", "")
	s = strings.ReplaceAll(s, "idr", "")
	s = strings.ReplaceAll(s, "usd", "")
	s = strings.ReplaceAll(s, " ", "")
	s = strings.ReplaceAll(s, "(", "-")
	s = strings.ReplaceAll(s, ")", "")
	s = strings.ReplaceAll(s, "'", "") // Handle stuff like 1'000

	// Strategy: If both . and , exist, the last one is likely the decimal separator
	if strings.Contains(s, ".") && strings.Contains(s, ",") {
		dotIdx := strings.LastIndex(s, ".")
		commaIdx := strings.LastIndex(s, ",")
		if dotIdx > commaIdx {
			// EN: 1,234,567.89
			s = strings.ReplaceAll(s, ",", "")
		} else {
			// ID: 1.234.567,89
			s = strings.ReplaceAll(s, ".", "")
			s = strings.ReplaceAll(s, ",", ".")
		}
	} else if strings.Count(s, ",") > 1 {
		// Clearly thousands separator: 1,234,567
		s = strings.ReplaceAll(s, ",", "")
	} else if strings.Count(s, ".") > 1 {
		// Clearly thousands separator: 1.234.567
		s = strings.ReplaceAll(s, ".", "")
	} else if strings.Contains(s, ",") {
		// Single comma, no dot. Is it 123,456 (TH) or 123,45 (DEC)?
		// If 3 digits after comma, highly likely Indonesian style thousands without dot or just one field
		parts := strings.Split(s, ",")
		if len(parts) == 2 && len(parts[1]) == 3 {
			// Ambiguous: could be US 123,456 or ID 123,456 (decimal - rare to have 3 dec digits in currency)
			// But currency usually has 0 or 2 decimal digits. 3 is almost always thousand separator.
			s = strings.ReplaceAll(s, ",", "")
		} else {
			s = strings.ReplaceAll(s, ",", ".")
		}
	} else if strings.Count(s, ".") == 1 {
		// Single dot, no comma. Is it 123.456 (ID TH) or 123.45 (US DEC)?
		parts := strings.Split(s, ".")
		if len(parts) == 2 && len(parts[1]) == 3 {
			// Likely ID thousand separator
			s = strings.ReplaceAll(s, ".", "")
		}
	}

	// Final extraction: Keep only numbers, minus, and one dot
	final := ""
	hasDot := false
	for _, r := range s {
		if r >= '0' && r <= '9' {
			final += string(r)
		} else if r == '-' && final == "" {
			final += string(r)
		} else if r == '.' && !hasDot {
			final += string(r)
			hasDot = true
		}
	}

	if v, err := strconv.ParseFloat(final, 64); err == nil {
		return v
	}
	return 0
}

// ParseInt removes non-numeric chars and parses to int
func ParseInt(val string) int {
	return int(math.Round(ParseFloat(val)))
}

// GenerateDocRef replaces placeholders in a template with actual values
func GenerateDocRef(template string, seq int, date time.Time) string {
	if template == "" {
		return fmt.Sprintf("%03d", seq)
	}
	res := template
	res = strings.ReplaceAll(res, "{YYYY}", date.Format("2006"))
	res = strings.ReplaceAll(res, "{YY}", date.Format("06"))
	res = strings.ReplaceAll(res, "{MM}", fmt.Sprintf("%02d", int(date.Month())))
	res = strings.ReplaceAll(res, "{ROM}", GetRomanMonth(int(date.Month())))
	res = strings.ReplaceAll(res, "{DD}", fmt.Sprintf("%02d", date.Day()))
	res = strings.ReplaceAll(res, "{INC}", fmt.Sprintf("%03d", seq))
	return res
}

// ParseParamID securely parses a URL parameter into uint, returning error if invalid
func ParseParamID(c *fiber.Ctx, key string) (uint, error) {
	val := c.Params(key)
	if val == "" {
		return 0, errors.New("parameter ID is empty")
	}
	idUint, err := strconv.ParseUint(val, 10, 32)
	if err != nil {
		return 0, errors.New("invalid parameter ID format")
	}
	if idUint == 0 {
		return 0, errors.New("parameter ID must be greater than zero")
	}
	return uint(idUint), nil
}

// FormatDateID returns date string in Indonesian format (e.g. 29 Januari 2026)
func FormatDateID(t time.Time) string {
	months := []string{
		"", "Januari", "Februari", "Maret", "April", "Mei", "Juni",
		"Juli", "Agustus", "September", "Oktober", "November", "Desember",
	}
	return fmt.Sprintf("%d %s %d", t.Day(), months[t.Month()], t.Year())
}

// ParseBody parses JSON request body into a target struct and returns structured error response on failure
func ParseBody(c *fiber.Ctx, out interface{}) error {
	if err := c.BodyParser(out); err != nil {
		return ErrorResponse(c, fiber.StatusBadRequest, "Invalid JSON format: " + err.Error())
	}
	return nil
}
