package admin

import (
	"encoding/json"
	"io"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gothinkster/golang-gin-realworld-example-app/common"
	"github.com/gothinkster/golang-gin-realworld-example-app/middleware"
	"github.com/gothinkster/golang-gin-realworld-example-app/models"
)

// PaginationParams holds parsed ProTable pagination query params
type PaginationParams struct {
	Current  int
	PageSize int
}

// ParsePagination reads ?current=N&pageSize=M from the request
func ParsePagination(c *gin.Context) PaginationParams {
	current, _ := strconv.Atoi(c.DefaultQuery("current", "1"))
	pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20"))
	if current < 1 {
		current = 1
	}
	if pageSize < 1 {
		pageSize = 20
	}
	if pageSize > 100 {
		pageSize = 100
	}
	return PaginationParams{Current: current, PageSize: pageSize}
}

// Offset returns the DB offset for GORM queries
func (p PaginationParams) Offset() int {
	return (p.Current - 1) * p.PageSize
}

// OK responds with {success: true, data: ...}
func OK(c *gin.Context, data interface{}) {
	c.JSON(http.StatusOK, gin.H{"success": true, "data": data})
}

// OKList responds with {success: true, data: [...], total: N} — used by ProTable
func OKList(c *gin.Context, data interface{}, total int64) {
	c.JSON(http.StatusOK, gin.H{"success": true, "data": data, "total": total})
}

// Fail responds with {success: false, message: ...}
func Fail(c *gin.Context, status int, msg string) {
	c.JSON(status, gin.H{"success": false, "message": msg})
}

// flexTimeLayouts is the set of time formats accepted from the frontend.
var flexTimeLayouts = []string{
	"2006-01-02T15:04:05",
	"2006-01-02 15:04:05",
	"2006-01-02T15:04",
	"2006-01-02 15:04",
	"2006-01-02",
}

// BindJSONFlexTime works like c.ShouldBindJSON but also accepts non-RFC3339
// time strings such as "2026-02-28 00:00:00" that Ant Design's DatePicker sends.
// It reads the raw body, converts any date-like strings to RFC3339, then unmarshals.
func BindJSONFlexTime(c *gin.Context, obj interface{}) error {
	body, err := io.ReadAll(c.Request.Body)
	if err != nil {
		return err
	}

	var raw map[string]json.RawMessage
	if err := json.Unmarshal(body, &raw); err != nil {
		return json.Unmarshal(body, obj) // fallback: let normal errors surface
	}

	for key, val := range raw {
		var s string
		if err := json.Unmarshal(val, &s); err != nil {
			continue // not a string, skip
		}
		for _, layout := range flexTimeLayouts {
			if t, err := time.Parse(layout, s); err == nil {
				converted, _ := json.Marshal(t) // produces RFC3339
				raw[key] = converted
				break
			}
		}
	}

	fixed, err := json.Marshal(raw)
	if err != nil {
		return err
	}
	return json.Unmarshal(fixed, obj)
}

// LogOp writes a record to operation_logs table
func LogOp(c *gin.Context, opType, targetType, action string, targetID *uint, payload interface{}) {
	db := common.GetDB()
	user, _ := middleware.GetCurrentUser(c)

	var userID *uint
	if user != nil {
		uid := uint(user.UserID)
		userID = &uid
	}

	var changesStr string
	if payload != nil {
		b, _ := json.Marshal(payload)
		changesStr = string(b)
	}

	db.Create(&models.OperationLog{
		UserID:        userID,
		OperationType: opType,
		TargetType:    targetType,
		Action:        action,
		TargetID:      targetID,
		Changes:       changesStr,
		IPAddress:     c.ClientIP(),
		UserAgent:     c.GetHeader("User-Agent"),
		Status:        "success",
		CreatedAt:     time.Now(),
	})
}
