package promo

import (
	"context"
	"testing"
	"time"

	promomodel "github.com/perfect-panel/server/internal/model/promo"
	"github.com/perfect-panel/server/internal/svc"
	"github.com/perfect-panel/server/internal/types"
	"github.com/perfect-panel/server/pkg/xerr"
	pkgerrors "github.com/pkg/errors"
	"github.com/redis/go-redis/v9"
)

func TestCreateRuleAcceptsMillisecondTimestamps(t *testing.T) {
	startTime := int64(1777618800000)
	endTime := int64(1782802800000)
	var inserted *promomodel.Rule

	svcCtx := &svc.ServiceContext{
		Redis: redis.NewClient(&redis.Options{Addr: "127.0.0.1:0"}),
		PromoModel: fakePromoModel{
			insertRule: func(_ context.Context, rule *promomodel.Rule) error {
				inserted = rule
				return nil
			},
		},
	}

	_, err := NewCreateRuleLogic(context.Background(), svcCtx).CreateRule(&types.CreatePromoRuleRequest{
		Name:      "618活动",
		Type:      promomodel.RuleTypeInactiveUser,
		Params:    map[string]interface{}{"inactive_months": float64(1)},
		Priority:  0,
		StartTime: &startTime,
		EndTime:   &endTime,
	})
	if err != nil {
		t.Fatalf("CreateRule returned error: %v", err)
	}
	if inserted == nil {
		t.Fatal("rule was not inserted")
	}
	assertPromoRuleTime(t, inserted.StartTime, time.UnixMilli(startTime))
	assertPromoRuleTime(t, inserted.EndTime, time.UnixMilli(endTime))
}

func TestUpdateRuleAcceptsMillisecondTimestamps(t *testing.T) {
	startTime := int64(1777618800000)
	endTime := int64(1782802800000)
	existing := &promomodel.Rule{Id: 9, Enabled: true}
	var updated *promomodel.Rule

	svcCtx := &svc.ServiceContext{
		Redis: redis.NewClient(&redis.Options{Addr: "127.0.0.1:0"}),
		PromoModel: fakePromoModel{
			findRule: func(_ context.Context, id int64) (*promomodel.Rule, error) {
				if id != existing.Id {
					t.Fatalf("FindRule id = %d, want %d", id, existing.Id)
				}
				return existing, nil
			},
			updateRule: func(_ context.Context, rule *promomodel.Rule) error {
				updated = rule
				return nil
			},
		},
	}

	_, err := NewUpdateRuleLogic(context.Background(), svcCtx).UpdateRule(&types.UpdatePromoRuleRequest{
		Id:        existing.Id,
		Name:      "618活动",
		Type:      promomodel.RuleTypeInactiveUser,
		Params:    map[string]interface{}{"inactive_months": float64(1)},
		Priority:  0,
		StartTime: &startTime,
		EndTime:   &endTime,
	})
	if err != nil {
		t.Fatalf("UpdateRule returned error: %v", err)
	}
	if updated == nil {
		t.Fatal("rule was not updated")
	}
	assertPromoRuleTime(t, updated.StartTime, time.UnixMilli(startTime))
	assertPromoRuleTime(t, updated.EndTime, time.UnixMilli(endTime))
}

func TestRuleRejectsOutOfRangeTimestamp(t *testing.T) {
	startTime := int64(253402300800000)
	endTime := int64(253402304400000)
	svcCtx := &svc.ServiceContext{PromoModel: fakePromoModel{
		insertRule: func(context.Context, *promomodel.Rule) error {
			t.Fatal("InsertRule should not be called for invalid timestamp")
			return nil
		},
	}}

	_, err := NewCreateRuleLogic(context.Background(), svcCtx).CreateRule(&types.CreatePromoRuleRequest{
		Name:      "bad time",
		Type:      promomodel.RuleTypeInactiveUser,
		Params:    map[string]interface{}{"inactive_months": float64(1)},
		Priority:  0,
		StartTime: &startTime,
		EndTime:   &endTime,
	})
	assertInvalidParams(t, err)
}

func assertPromoRuleTime(t *testing.T, got *time.Time, want time.Time) {
	t.Helper()
	if got == nil {
		t.Fatalf("time is nil, want %v", want)
	}
	if !got.Equal(want) {
		t.Fatalf("time = %v, want %v", *got, want)
	}
}

func assertInvalidParams(t *testing.T, err error) {
	t.Helper()
	if err == nil {
		t.Fatal("expected error")
	}
	codeErr, ok := pkgerrors.Cause(err).(*xerr.CodeError)
	if !ok {
		t.Fatalf("expected CodeError, got %T", pkgerrors.Cause(err))
	}
	if got := codeErr.GetErrCode(); got != xerr.InvalidParams {
		t.Fatalf("error code = %d, want %d", got, xerr.InvalidParams)
	}
}
