package promo

import (
	"encoding/json"
	"fmt"
	"math"
	"time"

	promomodel "github.com/perfect-panel/server/internal/model/promo"
	"github.com/perfect-panel/server/internal/types"
	"github.com/perfect-panel/server/pkg/xerr"
	"github.com/pkg/errors"
)

const (
	ruleCacheKey       = "promo:rules:enabled"
	subscribeCachePref = "promo:subscribe:"
)

var (
	minPromoRuleTime = time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)
	maxPromoRuleTime = time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC)
)

func validateRuleInput(ruleType string, params map[string]interface{}, priority int64, startTime, endTime *int64) error {
	if priority < 0 {
		return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "priority must be greater than or equal to 0")
	}
	startAt, err := normalizeRuleTimestamp(startTime)
	if err != nil {
		return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "invalid start_time")
	}
	endAt, err := normalizeRuleTimestamp(endTime)
	if err != nil {
		return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "invalid end_time")
	}
	if startAt != nil && endAt != nil && !startAt.Before(*endAt) {
		return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "start_time must be less than end_time")
	}
	switch ruleType {
	case "new_user":
		windowHours, ok := numberParam(params, "window_hours")
		if !ok || windowHours <= 0 {
			return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "params.window_hours must be greater than 0")
		}
	case "inactive_user":
		inactiveMonths, ok := numberParam(params, "inactive_months")
		if !ok || inactiveMonths <= 0 {
			return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "params.inactive_months must be greater than 0")
		}
	case "campaign":
		if params == nil {
			return nil
		}
	default:
		return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "unsupported promo rule type")
	}
	return nil
}

func numberParam(params map[string]interface{}, key string) (int64, bool) {
	if params == nil {
		return 0, false
	}
	value, ok := params[key]
	if !ok {
		return 0, false
	}
	switch v := value.(type) {
	case float64:
		if math.Trunc(v) != v {
			return 0, false
		}
		return int64(v), true
	case int64:
		return v, true
	case int:
		return int64(v), true
	case json.Number:
		n, err := v.Int64()
		return n, err == nil
	default:
		return 0, false
	}
}

func paramsToString(params map[string]interface{}) (string, error) {
	if params == nil {
		params = map[string]interface{}{}
	}
	b, err := json.Marshal(params)
	if err != nil {
		return "", errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "invalid params")
	}
	return string(b), nil
}

func parseParams(data string) map[string]interface{} {
	if data == "" {
		return map[string]interface{}{}
	}
	var params map[string]interface{}
	if err := json.Unmarshal([]byte(data), &params); err != nil {
		return map[string]interface{}{}
	}
	return params
}

func normalizeRuleTimestamp(ts *int64) (*time.Time, error) {
	if ts == nil || *ts == 0 {
		return nil, nil
	}
	value := *ts
	var t time.Time
	if value >= 1_000_000_000_000 || value <= -1_000_000_000_000 {
		t = time.UnixMilli(value)
	} else {
		t = time.Unix(value, 0)
	}
	if t.Before(minPromoRuleTime) || t.After(maxPromoRuleTime) {
		return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "timestamp out of range")
	}
	return &t, nil
}

func unixPtrToTimePtr(ts *int64) *time.Time {
	t, err := normalizeRuleTimestamp(ts)
	if err != nil {
		return nil
	}
	return t
}

func timePtrToUnixPtr(t *time.Time) *int64 {
	if t == nil {
		return nil
	}
	ts := t.Unix()
	return &ts
}

func convertRule(item *promomodel.Rule) types.PromoRule {
	if item == nil {
		return types.PromoRule{}
	}
	return types.PromoRule{
		Id:        item.Id,
		Name:      item.Name,
		Type:      item.Type,
		Params:    parseParams(item.Params),
		Priority:  item.Priority,
		Enabled:   item.Enabled,
		StartTime: timePtrToUnixPtr(item.StartTime),
		EndTime:   timePtrToUnixPtr(item.EndTime),
		CreatedAt: item.CreatedAt.Unix(),
		UpdatedAt: item.UpdatedAt.Unix(),
	}
}

func convertPrice(item *promomodel.SubscribePromo) types.PromoPrice {
	if item == nil {
		return types.PromoPrice{}
	}
	return types.PromoPrice{
		Id:          item.Id,
		SubscribeId: item.SubscribeId,
		PromoRuleId: item.PromoRuleId,
		Quantity:    item.Quantity,
		PromoPrice:  item.PromoPrice,
		CreatedAt:   item.CreatedAt.Unix(),
		UpdatedAt:   item.UpdatedAt.Unix(),
	}
}

func convertUsage(item *promomodel.Usage) types.PromoUsage {
	if item == nil {
		return types.PromoUsage{}
	}
	return types.PromoUsage{
		Id:          item.Id,
		UserId:      item.UserId,
		PromoRuleId: item.PromoRuleId,
		SubscribeId: item.SubscribeId,
		OrderNo:     item.OrderNo,
		PromoPrice:  item.PromoPrice,
		CreatedAt:   item.CreatedAt.Unix(),
	}
}

func subscribePromoCacheKey(subscribeId int64) string {
	return fmt.Sprintf("%s%d", subscribeCachePref, subscribeId)
}
