package handler

import (
	"encoding/json"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"
	"xboard-go/internal/database"
	"xboard-go/internal/model"

	"github.com/gin-gonic/gin"
)

func Success(c *gin.Context, data any) {
	c.JSON(http.StatusOK, gin.H{"data": data})
}

func SuccessMessage(c *gin.Context, message string, data any) {
	c.JSON(http.StatusOK, gin.H{
		"message": message,
		"data":    data,
	})
}

func Fail(c *gin.Context, status int, message string) {
	c.JSON(status, gin.H{"message": message})
}

func NotImplemented(endpoint string) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.JSON(http.StatusNotImplemented, gin.H{
			"message":  "not implemented yet",
			"endpoint": endpoint,
		})
	}
}

func intFromAny(value any) int {
	switch typed := value.(type) {
	case int:
		return typed
	case int8:
		return int(typed)
	case int16:
		return int(typed)
	case int32:
		return int(typed)
	case int64:
		return int(typed)
	case float32:
		return int(typed)
	case float64:
		return int(typed)
	case json.Number:
		parsed, _ := typed.Int64()
		return int(parsed)
	case string:
		parsed, _ := strconv.Atoi(strings.TrimSpace(typed))
		return parsed
	default:
		return 0
	}
}

func stringFromAny(value any) string {
	switch typed := value.(type) {
	case string:
		return typed
	case json.Number:
		return typed.String()
	case float64:
		return strconv.FormatFloat(typed, 'f', -1, 64)
	case float32:
		return strconv.FormatFloat(float64(typed), 'f', -1, 32)
	case int:
		return strconv.Itoa(typed)
	case int64:
		return strconv.FormatInt(typed, 10)
	case bool:
		if typed {
			return "true"
		}
		return "false"
	default:
		return ""
	}
}

func boolFromAny(value any) bool {
	switch typed := value.(type) {
	case bool:
		return typed
	case int:
		return typed != 0
	case int64:
		return typed != 0
	case float64:
		return typed != 0
	case string:
		text := strings.TrimSpace(strings.ToLower(typed))
		return text == "1" || text == "true" || text == "yes" || text == "on"
	default:
		return false
	}
}

func intValue(value *int) any {
	if value == nil {
		return nil
	}
	return *value
}

func int64Value(value *int64) any {
	if value == nil {
		return nil
	}
	return *value
}

func stringValue(value *string) string {
	if value == nil {
		return ""
	}
	return *value
}

func unixTimeValue(value *time.Time) any {
	if value == nil {
		return nil
	}
	return value.Unix()
}

func marshalJSON(value any, fallbackEmptyArray bool) (*string, error) {
	if value == nil {
		if fallbackEmptyArray {
			empty := "[]"
			return &empty, nil
		}
		return nil, nil
	}

	if text, ok := value.(string); ok {
		text = strings.TrimSpace(text)
		if text == "" {
			if fallbackEmptyArray {
				empty := "[]"
				return &empty, nil
			}
			return nil, nil
		}
		if json.Valid([]byte(text)) {
			return &text, nil
		}
	}

	payload, err := json.Marshal(value)
	if err != nil {
		return nil, err
	}
	text := string(payload)
	return &text, nil
}

func sanitizePositiveIDs(ids []int) []int {
	unique := make(map[int]struct{}, len(ids))
	result := make([]int, 0, len(ids))
	for _, id := range ids {
		if id <= 0 {
			continue
		}
		if _, exists := unique[id]; exists {
			continue
		}
		unique[id] = struct{}{}
		result = append(result, id)
	}
	sort.Ints(result)
	return result
}

func loadServerGroupNameMap() map[int]string {
	var groups []model.ServerGroup
	_ = database.DB.Find(&groups).Error

	result := make(map[int]string, len(groups))
	for _, group := range groups {
		result[group.ID] = group.Name
	}
	return result
}

func loadUserEmailMap(userIDs []int) map[int]string {
	result := make(map[int]string)
	if len(userIDs) == 0 {
		return result
	}

	var users []model.User
	if err := database.DB.Select("id", "email").Where("id IN ?", sanitizePositiveIDs(userIDs)).Find(&users).Error; err != nil {
		return result
	}

	for _, user := range users {
		result[user.ID] = user.Email
	}
	return result
}

func loadTicketMessageCountMap(ticketIDs []int) map[int]int64 {
	result := make(map[int]int64)
	if len(ticketIDs) == 0 {
		return result
	}

	type ticketCount struct {
		TicketID int
		Total    int64
	}
	var counts []ticketCount
	if err := database.DB.Model(&model.TicketMessage{}).
		Select("ticket_id, COUNT(*) AS total").
		Where("ticket_id IN ?", sanitizePositiveIDs(ticketIDs)).
		Group("ticket_id").
		Scan(&counts).Error; err != nil {
		return result
	}

	for _, item := range counts {
		result[item.TicketID] = item.Total
	}
	return result
}

func parsePositiveInt(raw string, defaultValue int) int {
	value, err := strconv.Atoi(strings.TrimSpace(raw))
	if err != nil || value <= 0 {
		return defaultValue
	}
	return value
}

func calculateLastPage(total int64, perPage int) int {
	if perPage <= 0 {
		return 1
	}
	last := int((total + int64(perPage) - 1) / int64(perPage))
	if last == 0 {
		return 1
	}
	return last
}

func formatUnixValue(value int64) string {
	if value <= 0 {
		return "-"
	}
	return time.Unix(value, 0).Format("2006-01-02 15:04:05")
}

func formatTimeValue(value *time.Time) string {
	if value == nil {
		return "-"
	}
	return value.Format("2006-01-02 15:04:05")
}

func getFetchParams(c *gin.Context) map[string]string {
	params := make(map[string]string)
	
	// 1. Get from Query parameters
	for k, v := range c.Request.URL.Query() {
		if len(v) > 0 {
			params[k] = v[0]
		}
	}
	
	// 2. Override with JSON body if applicable (for POST)
	if c.Request.Method == http.MethodPost {
		var body map[string]any
		// Using ShouldBindJSON without erroring to allow fallback or partial data
		if err := c.ShouldBindJSON(&body); err == nil {
			for k, v := range body {
				params[k] = stringFromAny(v)
			}
		}
	}
	
	return params
}
