package appconfig

import (
	"testing"
	"time"

	"hyapp-admin-server/internal/model"
)

func TestH5LinkModelFromPayloadValidatesDynamicConfig(t *testing.T) {
	item, err := h5LinkModelFromPayload(h5LinkPayload{
		Key:   "host-center.v2",
		Label: "Host Center",
		URL:   "https://h5.example.com/host",
	})
	if err != nil {
		t.Fatalf("h5 config should be valid: %v", err)
	}
	if item.Group != h5LinkGroup || item.Key != "host-center.v2" || item.Description != "Host Center" || item.Value != "https://h5.example.com/host" {
		t.Fatalf("h5 config model mismatch: %+v", item)
	}
}

func TestH5LinkModelFromPayloadRejectsInvalidKey(t *testing.T) {
	_, err := h5LinkModelFromPayload(h5LinkPayload{
		Key:   "host center",
		Label: "Host Center",
		URL:   "https://h5.example.com/host",
	})
	if err == nil {
		t.Fatal("expected invalid key to fail")
	}
}

func TestBannerModelFromRequestRequiresRoomSmallImageForRoomScope(t *testing.T) {
	_, err := bannerModelFromRequest("lalu", bannerRequest{
		CoverURL:     "https://cdn.example.com/banner.png",
		BannerType:   "h5",
		DisplayScope: bannerDisplayScopeRoom,
		Param:        "https://h5.example.com/activity",
		Status:       bannerStatusActive,
		Platform:     "android",
		EndsAtMs:     time.Now().UTC().Add(time.Hour).UnixMilli(),
	})
	if err == nil {
		t.Fatal("expected room scope without room small image to fail")
	}
}

func TestBannerModelFromRequestKeepsRoomScopeScheduleFields(t *testing.T) {
	startsAtMs := time.Now().UTC().Add(time.Hour).UnixMilli()
	endsAtMs := startsAtMs + int64(time.Hour/time.Millisecond)
	item, err := bannerModelFromRequest("lalu", bannerRequest{
		CoverURL:          "https://cdn.example.com/banner.png",
		RoomSmallImageURL: "https://cdn.example.com/banner-small.png",
		BannerType:        "h5",
		DisplayScopes:     []string{bannerDisplayScopeHome, bannerDisplayScopeRoom},
		Param:             "https://h5.example.com/activity",
		Status:            bannerStatusActive,
		Platform:          "android",
		StartsAtMs:        startsAtMs,
		EndsAtMs:          endsAtMs,
	})
	if err != nil {
		t.Fatalf("banner model should be valid: %v", err)
	}
	if item.DisplayScope != "home,room" || item.RoomSmallImageURL == "" {
		t.Fatalf("room display fields mismatch: %+v", item)
	}
	if item.StartsAtMS != startsAtMs || item.EndsAtMS != endsAtMs || item.Status != bannerStatusActive {
		t.Fatalf("schedule fields mismatch: %+v", item)
	}
}

func TestBannerModelFromRequestExpiresEndedActiveBanner(t *testing.T) {
	item, err := bannerModelFromRequest("lalu", bannerRequest{
		CoverURL:          "https://cdn.example.com/banner.png",
		RoomSmallImageURL: "https://cdn.example.com/ignored.png",
		BannerType:        "app",
		DisplayScope:      bannerDisplayScopeHome,
		Status:            bannerStatusActive,
		Platform:          "ios",
		EndsAtMs:          1,
	})
	if err != nil {
		t.Fatalf("banner model should accept ended active banner: %v", err)
	}
	if item.Status != bannerStatusExpired {
		t.Fatalf("ended active banner must become expired: %+v", item)
	}
	if item.RoomSmallImageURL != "" {
		t.Fatalf("non-room banner must not retain room small image: %+v", item)
	}
}

func TestAppBannerFromModelReturnsDisplayScopes(t *testing.T) {
	item := appBannerFromModel(bannerModel("home,room,recharge,me"))

	if item.DisplayScope != "home,room,recharge,me" {
		t.Fatalf("display scope should keep stored value: %+v", item)
	}
	if len(item.DisplayScopes) != 4 || item.DisplayScopes[0] != bannerDisplayScopeHome || item.DisplayScopes[1] != bannerDisplayScopeRoom || item.DisplayScopes[2] != bannerDisplayScopeRecharge || item.DisplayScopes[3] != bannerDisplayScopeMe {
		t.Fatalf("display scopes mismatch: %+v", item.DisplayScopes)
	}
}

func TestSplashScreenModelFromRequestValidatesH5Config(t *testing.T) {
	startsAtMs := time.Now().UTC().Add(time.Hour).UnixMilli()
	endsAtMs := startsAtMs + int64(time.Hour/time.Millisecond)
	item, err := splashScreenModelFromRequest("lalu", splashScreenRequest{
		CoverURL:          "https://cdn.example.com/splash.png",
		SplashType:        "h5",
		Param:             "https://h5.example.com/splash",
		Status:            bannerStatusActive,
		Platform:          "android",
		SortOrder:         7,
		RegionID:          1001,
		CountryCode:       "us",
		Description:       "launch campaign",
		DisplayDurationMs: 4200,
		StartsAtMs:        startsAtMs,
		EndsAtMs:          endsAtMs,
	})
	if err != nil {
		t.Fatalf("splash screen model should be valid: %v", err)
	}
	if item.AppCode != "lalu" || item.SplashType != "h5" || item.CountryCode != "US" || item.DisplayDurationMS != 4200 || item.StartsAtMS != startsAtMs || item.EndsAtMS != endsAtMs {
		t.Fatalf("splash screen model mismatch: %+v", item)
	}
}

func TestSplashScreenModelFromRequestDefaultsDisplayDuration(t *testing.T) {
	item, err := splashScreenModelFromRequest("lalu", splashScreenRequest{
		CoverURL:   "https://cdn.example.com/splash.png",
		SplashType: "app",
		Status:     bannerStatusActive,
		Platform:   "android",
	})
	if err != nil {
		t.Fatalf("splash screen model should accept missing display duration: %v", err)
	}
	if item.DisplayDurationMS != splashDefaultDisplayDurationMS {
		t.Fatalf("display duration should default to %d, got %+v", splashDefaultDisplayDurationMS, item)
	}
}

func TestSplashScreenModelFromRequestRejectsNegativeDisplayDuration(t *testing.T) {
	_, err := splashScreenModelFromRequest("lalu", splashScreenRequest{
		CoverURL:          "https://cdn.example.com/splash.png",
		SplashType:        "app",
		Status:            bannerStatusActive,
		Platform:          "android",
		DisplayDurationMs: -1,
	})
	if err == nil {
		t.Fatal("expected negative display duration to fail")
	}
}

func TestSplashScreenModelFromRequestRequiresH5Link(t *testing.T) {
	_, err := splashScreenModelFromRequest("lalu", splashScreenRequest{
		CoverURL:   "https://cdn.example.com/splash.png",
		SplashType: "h5",
		Status:     bannerStatusActive,
		Platform:   "android",
	})
	if err == nil {
		t.Fatal("expected h5 splash screen without link to fail")
	}
}

func TestSplashScreenModelFromRequestExpiresEndedActiveConfig(t *testing.T) {
	item, err := splashScreenModelFromRequest("lalu", splashScreenRequest{
		CoverURL:   "https://cdn.example.com/splash.png",
		SplashType: "app",
		Status:     bannerStatusActive,
		Platform:   "ios",
		EndsAtMs:   1,
	})
	if err != nil {
		t.Fatalf("ended app splash screen should be accepted: %v", err)
	}
	if item.Status != bannerStatusExpired {
		t.Fatalf("ended active splash screen must become expired: %+v", item)
	}
}

func TestAppSplashScreenFromModelMapsDTOFields(t *testing.T) {
	item := appSplashScreenFromModel(model.AppSplashScreen{
		ID:                9,
		AppCode:           "lalu",
		CoverURL:          "https://cdn.example.com/splash.png",
		SplashType:        "app",
		Param:             "profile",
		Status:            bannerStatusDisabled,
		Platform:          "ios",
		SortOrder:         3,
		RegionID:          2002,
		CountryCode:       "BR",
		Description:       "disabled campaign",
		DisplayDurationMS: 5000,
		StartsAtMS:        1700000000000,
		EndsAtMS:          1800000000000,
		CreatedAtMS:       1690000000000,
		UpdatedAtMS:       1700000002000,
	})

	if item.ID != 9 || item.SplashType != "app" || item.Param != "profile" || item.RegionID != 2002 || item.DisplayDurationMs != 5000 || item.UpdatedAtMs != 1700000002000 {
		t.Fatalf("splash screen dto mismatch: %+v", item)
	}
}

func TestPopupModelFromRequestValidatesOptionalImageAndPeriod(t *testing.T) {
	startsAtMs := time.Now().UTC().Add(time.Hour).UnixMilli()
	endsAtMs := startsAtMs + int64(time.Hour/time.Millisecond)
	item, err := popupModelFromRequest("lalu", popupRequest{
		Code:              "new-user-popup",
		Name:              "新人活动",
		ImageURL:          "",
		JumpType:          "h5",
		JumpURL:           "https://h5.example.com/new-user",
		DisplayPeriodDays: 2,
		Status:            bannerStatusActive,
		SortOrder:         4,
		StartsAtMs:        startsAtMs,
		EndsAtMs:          endsAtMs,
	})
	if err != nil {
		t.Fatalf("popup model should be valid: %v", err)
	}
	if item.AppCode != "lalu" || item.Code != "new-user-popup" || item.Name != "新人活动" || item.ImageURL != "" || item.JumpType != "h5" || item.DisplayPeriodDays != 2 || item.StartsAtMS != startsAtMs || item.EndsAtMS != endsAtMs {
		t.Fatalf("popup model mismatch: %+v", item)
	}
}

func TestPopupModelFromRequestRejectsInvalidJumpURL(t *testing.T) {
	_, err := popupModelFromRequest("lalu", popupRequest{
		Code:     "activity-popup",
		Name:     "活动弹窗",
		JumpType: "h5",
		JumpURL:  "https://h5.example.com/new user",
		Status:   bannerStatusActive,
	})
	if err == nil {
		t.Fatal("expected popup h5 jump url with whitespace to fail")
	}
}

func TestPopupModelFromRequestRejectsInvalidCode(t *testing.T) {
	_, err := popupModelFromRequest("lalu", popupRequest{
		Code:     "activity popup",
		Name:     "活动弹窗",
		JumpType: "app",
		JumpURL:  "activity/detail",
		Status:   bannerStatusActive,
	})
	if err == nil {
		t.Fatal("expected popup code with whitespace to fail")
	}
}

func TestPopupModelFromRequestExpiresEndedActivePopup(t *testing.T) {
	item, err := popupModelFromRequest("lalu", popupRequest{
		Code:     "history-popup",
		Name:     "历史活动",
		JumpType: "app",
		JumpURL:  "activity/detail",
		Status:   bannerStatusActive,
		EndsAtMs: 1,
	})
	if err != nil {
		t.Fatalf("ended popup should be accepted: %v", err)
	}
	if item.Status != bannerStatusExpired {
		t.Fatalf("ended active popup must become expired: %+v", item)
	}
}

func TestAppPopupFromModelMapsDTOFields(t *testing.T) {
	item := appPopupFromModel(model.AppPopup{
		ID:                10,
		AppCode:           "lalu",
		Code:              "recharge-popup",
		Name:              "充值活动",
		ImageURL:          "https://cdn.example.com/popup.png",
		JumpType:          "app",
		JumpURL:           "recharge",
		DisplayPeriodDays: 0,
		Status:            bannerStatusDisabled,
		SortOrder:         8,
		StartsAtMS:        1700000000000,
		EndsAtMS:          1800000000000,
		CreatedAtMS:       1690000000000,
		UpdatedAtMS:       1700000002000,
	})

	if item.ID != 10 || item.Code != "recharge-popup" || item.Name != "充值活动" || item.JumpType != "app" || item.JumpURL != "recharge" || item.DisplayPeriodDays != 0 || item.UpdatedAtMs != 1700000002000 {
		t.Fatalf("popup dto mismatch: %+v", item)
	}
}

func bannerModel(displayScope string) model.AppBanner {
	return model.AppBanner{
		ID:           1,
		AppCode:      "lalu",
		CoverURL:     "https://cdn.example.com/banner.png",
		BannerType:   "h5",
		DisplayScope: displayScope,
		Param:        "https://h5.example.com/activity",
		Status:       bannerStatusActive,
		Platform:     "android",
	}
}

func TestExploreTabModelFromRequestValidatesRequiredFields(t *testing.T) {
	item, err := exploreTabModelFromRequest("lalu", exploreTabRequest{
		Enabled:   true,
		H5URL:     "https://h5.example.com/explore/live",
		SortOrder: 3,
		Tab:       "Live",
	})
	if err != nil {
		t.Fatalf("explore tab should be valid: %v", err)
	}
	if item.AppCode != "lalu" || item.Tab != "Live" || item.H5URL != "https://h5.example.com/explore/live" || !item.Enabled || item.SortOrder != 3 {
		t.Fatalf("explore tab model mismatch: %+v", item)
	}
}

func TestExploreTabModelFromRequestRejectsWhitespaceURL(t *testing.T) {
	_, err := exploreTabModelFromRequest("lalu", exploreTabRequest{
		Enabled: true,
		H5URL:   "https://h5.example.com/explore live",
		Tab:     "Live",
	})
	if err == nil {
		t.Fatal("expected whitespace h5 url to fail")
	}
}
