// query_timezone.go parses and validates timezone query parameters.
// Layer: adapters/inbound/httpapi request binding helpers.
package httpapi

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	apierrors "gitea.maximumdirect.net/ejr/feedapi/errors"
)

const maxUTCOffsetSeconds = 14 * 60 * 60

var usTimezoneAbbreviations = map[string]int{
	"CDT": -5 * 60 * 60,
	"CST": -6 * 60 * 60,
	"EDT": -4 * 60 * 60,
	"EST": -5 * 60 * 60,
	"MDT": -6 * 60 * 60,
	"MST": -7 * 60 * 60,
	"PDT": -7 * 60 * 60,
	"PST": -8 * 60 * 60,
}

var timezoneAliases = map[string]string{
	"chicago": "America/Chicago",
	"stl":     "America/Chicago",
}

func parseTimezoneQuery(r *http.Request) (*time.Location, error) {
	lower := strings.TrimSpace(r.URL.Query().Get("tz"))
	upper := strings.TrimSpace(r.URL.Query().Get("TZ"))
	if lower != "" && upper != "" && !strings.EqualFold(lower, upper) {
		return nil, apierrors.InvalidParameter("tz and TZ must match when both are provided")
	}

	raw := lower
	if raw == "" {
		raw = upper
	}
	if raw == "" {
		return nil, nil
	}

	loc, err := parseTimezoneValue(raw)
	if err != nil {
		return nil, apierrors.InvalidParameter(err.Error())
	}
	return loc, nil
}

func parseTimezoneValue(raw string) (*time.Location, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil, nil
	}

	if offsetSeconds, ok, err := parseUTCOffsetSeconds(raw); ok {
		if err != nil {
			return nil, err
		}
		return time.FixedZone(formatUTCOffsetName(offsetSeconds), offsetSeconds), nil
	}

	if offsetSeconds, ok := usTimezoneAbbreviations[strings.ToUpper(raw)]; ok {
		return time.FixedZone(strings.ToUpper(raw), offsetSeconds), nil
	}

	if alias, ok := timezoneAliases[strings.ToLower(raw)]; ok {
		raw = alias
	}

	loc, err := time.LoadLocation(raw)
	if err != nil {
		return nil, fmt.Errorf("tz must be a valid timezone")
	}
	return loc, nil
}

func parseUTCOffsetSeconds(raw string) (int, bool, error) {
	if len(raw) < 2 {
		return 0, false, nil
	}
	sign := raw[0]
	if sign != '+' && sign != '-' {
		return 0, false, nil
	}

	remainder := raw[1:]
	parts := strings.Split(remainder, ":")
	if len(parts) > 2 {
		return 0, true, fmt.Errorf("tz offset must be in ±H, ±HH, or ±HH:MM format")
	}

	hours, err := strconv.Atoi(parts[0])
	if err != nil {
		return 0, true, fmt.Errorf("tz offset must be in ±H, ±HH, or ±HH:MM format")
	}
	if hours < 0 {
		return 0, true, fmt.Errorf("tz offset must be in ±H, ±HH, or ±HH:MM format")
	}

	minutes := 0
	if len(parts) == 2 {
		if len(parts[1]) != 2 {
			return 0, true, fmt.Errorf("tz offset must be in ±H, ±HH, or ±HH:MM format")
		}
		minutes, err = strconv.Atoi(parts[1])
		if err != nil {
			return 0, true, fmt.Errorf("tz offset must be in ±H, ±HH, or ±HH:MM format")
		}
	}
	if minutes < 0 || minutes > 59 {
		return 0, true, fmt.Errorf("tz offset minutes must be between 00 and 59")
	}

	offset := (hours * 60 * 60) + (minutes * 60)
	if sign == '-' {
		offset = -offset
	}
	if offset < -maxUTCOffsetSeconds || offset > maxUTCOffsetSeconds {
		return 0, true, fmt.Errorf("tz offset must be between -14 and +14 hours")
	}

	return offset, true, nil
}

func formatUTCOffsetName(offsetSeconds int) string {
	sign := "+"
	if offsetSeconds < 0 {
		sign = "-"
		offsetSeconds = -offsetSeconds
	}
	hours := offsetSeconds / 3600
	minutes := (offsetSeconds % 3600) / 60
	return fmt.Sprintf("UTC%s%02d:%02d", sign, hours, minutes)
}
