package provider

import (
	"regexp"
	"strconv"
	"strings"
	"time"

	calent "github.com/tidbcloud/devops/entity/k8scalendar"
)

var (
	gkeQuarterRe    = regexp.MustCompile(`(?i)^\d{4}-Q[1-4]$`)
	gkeISODayRe     = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}`)
	gkeISOMonthRe   = regexp.MustCompile(`^\d{4}-\d{2}$`)
	gkeNamedDayRe   = regexp.MustCompile(`^[A-Za-z]+\s+\d{1,2},\s+\d{4}$`)
	gkeNamedMonthRe = regexp.MustCompile(`^[A-Za-z]+\s+\d{4}$`)
)

var versionRe = regexp.MustCompile(`1\.\d+`)

func normalizeVersion(v string) string {
	m := versionRe.FindString(v)
	return m
}

func parseLooseDate(str string) *time.Time {
	if str == "" {
		return nil
	}
	s := strings.TrimSpace(regexp.MustCompile(`\s+`).ReplaceAllString(str, " "))
	if t, err := time.Parse("January 2, 2006", s); err == nil {
		return &t
	}
	if t, err := time.Parse("Jan 2, 2006", s); err == nil {
		return &t
	}
	if t, err := time.Parse("2006-01-02", s); err == nil {
		return &t
	}
	if t, err := time.Parse("2006-01", s); err == nil {
		return &t
	}

	// By Azure: https://learn.microsoft.com/en-us/azure/aks/supported-kubernetes-versions?tabs=azure-cli#aks-kubernetes-release-calendar
	// the End of Life (EOL) date is the last day of the specified month. For example, "Mar 2026" indicates March 31, 2026.
	monthOnly := regexp.MustCompile(`^([A-Za-z]+)\s+(\d{4})$`).FindStringSubmatch(s)
	if len(monthOnly) == 3 {
		test, err := time.Parse("January 2, 2006", monthOnly[1]+" 1, "+monthOnly[2])
		if err == nil {
			last := time.Date(test.Year(), test.Month()+1, 0, 0, 0, 0, 0, time.UTC)
			return &last
		}
	}
	return nil
}

func parseQuarterOrDate(str string) *time.Time {
	if str == "" {
		return nil
	}
	s := strings.TrimSpace(regexp.MustCompile(`\s+`).ReplaceAllString(str, " "))
	q := regexp.MustCompile(`^(\d{4})-Q([1-4])$`).FindStringSubmatch(strings.ToUpper(s))
	if len(q) == 3 {
		year, _ := strconv.Atoi(q[1])
		quarter, _ := strconv.Atoi(q[2])
		if year > 0 && quarter >= 1 && quarter <= 4 {
			monthIndex := quarter*3 - 2 // Q1 -> Jan, Q2 -> Apr, Q3 -> Jul, Q4 -> Oct
			t := time.Date(year, time.Month(monthIndex), 0, 0, 0, 0, 0, time.UTC)
			return &t
		}
	}
	return parseLooseDate(s)
}

// gkeDatePrecision classifies a cleaned GKE schedule cell before parsing.
func gkeDatePrecision(cleaned string) string {
	if cleaned == "" {
		return ""
	}
	s := strings.TrimSpace(cleaned)
	u := strings.ToUpper(s)
	switch {
	case gkeQuarterRe.MatchString(u):
		return calent.ReleasePrecisionQuarter
	case gkeISODayRe.MatchString(s), gkeNamedDayRe.MatchString(s):
		return calent.ReleasePrecisionDay
	case gkeISOMonthRe.MatchString(s), gkeNamedMonthRe.MatchString(s):
		return calent.ReleasePrecisionMonth
	default:
		// Unrecognized text: do not treat as day-level.
		return ""
	}
}

func parseGkeScheduleDate(cleaned string) (*time.Time, string) {
	prec := gkeDatePrecision(cleaned)
	if prec == "" {
		return nil, ""
	}
	t := parseQuarterOrDate(cleaned)
	if t == nil {
		return nil, ""
	}
	return t, prec
}

func coarsestGkePrecision(parts ...string) string {
	rank := map[string]int{
		calent.ReleasePrecisionQuarter: 0,
		calent.ReleasePrecisionMonth:   1,
		calent.ReleasePrecisionDay:     2,
		"":                             3,
	}
	worst := ""
	worstRank := rank[worst]
	for _, p := range parts {
		if p == "" {
			continue
		}
		r, ok := rank[p]
		if !ok {
			continue
		}
		if r < worstRank {
			worstRank = r
			worst = p
		}
	}
	return worst
}

func cleanGkeScheduleCell(raw string) string {
	s := strings.TrimSpace(regexp.MustCompile(`\s+`).ReplaceAllString(raw, " "))
	if s == "" {
		return ""
	}
	if m := regexp.MustCompile(`^(\d{4}-Q[1-4])`).FindStringSubmatch(strings.ToUpper(s)); len(m) > 1 {
		return m[1]
	}
	if m := regexp.MustCompile(`^(\d{4}-\d{2}-\d{2})`).FindStringSubmatch(s); len(m) > 1 {
		tail := strings.ReplaceAll(s[len(m[1]):], " ", "")
		if tail == "" || regexp.MustCompile(`^\d+$`).MatchString(tail) {
			return m[1]
		}
		return m[1]
	}
	if m := regexp.MustCompile(`^(\d{4}-\d{2}-\d{2})\s+(\d+)$`).FindStringSubmatch(s); len(m) > 1 {
		return m[1]
	}
	return s
}
