package user

import (
	"context"
	"strings"
	"testing"
	"time"

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	hostdomain "hyapp/services/user-service/internal/domain/host"
	userdomain "hyapp/services/user-service/internal/domain/user"
)

type fakeCountryRegionRepository struct {
	countries map[string]userdomain.Country
	regions   map[string]userdomain.Region
}

func (r fakeCountryRegionRepository) ResolveEnabledCountryByCode(_ context.Context, countryCode string) (userdomain.Country, bool, error) {
	country, ok := r.countries[countryCode]
	return country, ok, nil
}

func (r fakeCountryRegionRepository) ListRegistrationCountries(context.Context) ([]userdomain.Country, error) {
	return nil, nil
}

func (r fakeCountryRegionRepository) ResolveActiveRegionByCountry(_ context.Context, countryCode string) (userdomain.Region, bool, error) {
	region, ok := r.regions[countryCode]
	return region, ok, nil
}

type fakeRoleSummaryRepository struct {
	summary hostdomain.UserRoleSummary
}

func (r fakeRoleSummaryRepository) GetUserRoleSummary(context.Context, int64) (hostdomain.UserRoleSummary, error) {
	return r.summary, nil
}

func TestChangeUserCountryAllowsNormalUserWithoutCooldown(t *testing.T) {
	ctx := appcode.WithContext(context.Background(), "lalu")
	repository := &fakeModerationRepository{user: userdomain.User{
		AppCode:          "lalu",
		UserID:           10001,
		Country:          "US",
		RegionID:         1,
		ProfileCompleted: true,
	}, countrySessionIDs: []string{"country_sess"}}
	denylist := &fakeSessionDenylist{}
	svc := New(repository,
		WithCountryRegionRepository(fakeCountryRegionRepository{
			countries: map[string]userdomain.Country{
				"ID": {CountryCode: "ID", Enabled: true},
				"TH": {CountryCode: "TH", Enabled: true},
			},
			regions: map[string]userdomain.Region{
				"ID": {RegionID: 2, Status: userdomain.RegionStatusActive},
				"TH": {RegionID: 3, Status: userdomain.RegionStatusActive},
			},
		}),
		WithRoleSummaryRepository(fakeRoleSummaryRepository{}),
		WithSessionDenylist(denylist, time.Minute),
		WithClock(func() time.Time { return time.UnixMilli(1_700_000_000_000).UTC() }),
	)

	if _, nextAllowedAt, err := svc.ChangeUserCountry(ctx, 10001, "ID", "req-id"); err != nil || nextAllowedAt != 0 {
		t.Fatalf("first normal country change failed: next=%d err=%v", nextAllowedAt, err)
	}
	if repository.lastCountryCommand.CooldownMs != 0 || repository.lastCountryCommand.Source != "app" || repository.lastCountryCommand.NewRegionID != 2 {
		t.Fatalf("app change command mismatch: %+v", repository.lastCountryCommand)
	}
	if _, nextAllowedAt, err := svc.ChangeUserCountry(ctx, 10001, "TH", "req-th"); err != nil || nextAllowedAt != 0 {
		t.Fatalf("second normal country change must not hit cooldown: next=%d err=%v", nextAllowedAt, err)
	}
	if repository.countryChangeCalls != 2 {
		t.Fatalf("repository must receive both app changes, calls=%d", repository.countryChangeCalls)
	}
	if len(denylist.sessions) != 2 || denylist.sessions[0] != "country_sess" || denylist.reasons[0] != countryChangeRevokeReason {
		t.Fatalf("country change must write revoked sessions to denylist: sessions=%+v reasons=%+v", denylist.sessions, denylist.reasons)
	}
}

func TestChangeUserCountryRejectsProtectedRoles(t *testing.T) {
	cases := []struct {
		name    string
		summary hostdomain.UserRoleSummary
		role    string
	}{
		{name: "host", summary: hostdomain.UserRoleSummary{IsHost: true}, role: "host"},
		{name: "agency", summary: hostdomain.UserRoleSummary{IsAgency: true}, role: "agency"},
		{name: "bd leader", summary: hostdomain.UserRoleSummary{IsBDLeader: true, IsBD: true}, role: "bd leader"},
		{name: "bd", summary: hostdomain.UserRoleSummary{IsBD: true}, role: "bd"},
		{name: "manager", summary: hostdomain.UserRoleSummary{IsManager: true}, role: "manager"},
		{name: "coin seller", summary: hostdomain.UserRoleSummary{IsCoinSeller: true}, role: "coin seller"},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			repository := &fakeModerationRepository{user: userdomain.User{
				AppCode:          "lalu",
				UserID:           10001,
				Country:          "US",
				RegionID:         1,
				ProfileCompleted: true,
			}}
			svc := New(repository,
				WithCountryRegionRepository(fakeCountryRegionRepository{
					countries: map[string]userdomain.Country{"ID": {CountryCode: "ID", Enabled: true}},
					regions:   map[string]userdomain.Region{"ID": {RegionID: 2, Status: userdomain.RegionStatusActive}},
				}),
				WithRoleSummaryRepository(fakeRoleSummaryRepository{summary: tc.summary}),
			)

			_, _, err := svc.ChangeUserCountry(appcode.WithContext(context.Background(), "lalu"), 10001, "ID", "req-protected")
			if xerr.CodeOf(err) != xerr.CountryChangeIdentityLocked {
				t.Fatalf("protected role must return identity lock: role=%s err=%v", tc.role, err)
			}
			want := "You already have a host identity. Please contact customer service to change your country."
			if !strings.Contains(err.Error(), want) {
				t.Fatalf("protected role message mismatch: got=%q want contains=%q", err.Error(), want)
			}
			if repository.countryChangeCalls != 0 {
				t.Fatalf("protected role must not update repository, calls=%d", repository.countryChangeCalls)
			}
		})
	}
}

func TestUpdateUserProfileBackgroundPersistsProfileBgImg(t *testing.T) {
	repository := &fakeModerationRepository{user: userdomain.User{
		AppCode:          "lalu",
		UserID:           10001,
		ProfileCompleted: true,
	}}
	svc := New(repository, WithClock(func() time.Time {
		return time.UnixMilli(1_700_000_000_000).UTC()
	}))
	profileBgImg := " https://cdn.example.com/profile-bg.png "

	updated, err := svc.UpdateUserProfileBackground(appcode.WithContext(context.Background(), "lalu"), 10001, profileBgImg)
	if err != nil {
		t.Fatalf("UpdateUserProfileBackground failed: %v", err)
	}

	if updated.ProfileBgImg != "https://cdn.example.com/profile-bg.png" {
		t.Fatalf("updated profile bg mismatch: %+v", updated)
	}
}

func TestUpdateUserProfileBackgroundRejectsInvalidProfileBgImg(t *testing.T) {
	repository := &fakeModerationRepository{user: userdomain.User{
		AppCode:          "lalu",
		UserID:           10001,
		ProfileCompleted: true,
	}}
	svc := New(repository)
	profileBgImg := "profile_card_gold"

	_, err := svc.UpdateUserProfileBackground(appcode.WithContext(context.Background(), "lalu"), 10001, profileBgImg)
	if xerr.CodeOf(err) != xerr.InvalidArgument {
		t.Fatalf("invalid profile_bg_img must be rejected, err=%v", err)
	}
}

func TestUpdateUserContactInfoPersistsUserContact(t *testing.T) {
	repository := &fakeModerationRepository{user: userdomain.User{
		AppCode:          "lalu",
		UserID:           10001,
		ProfileCompleted: true,
	}}
	svc := New(repository)

	updated, err := svc.UpdateUserContactInfo(appcode.WithContext(context.Background(), "lalu"), 10001, " +65 1234 5678 ")
	if err != nil {
		t.Fatalf("UpdateUserContactInfo failed: %v", err)
	}

	if updated.ContactInfo != "+65 1234 5678" {
		t.Fatalf("updated contact mismatch: %+v", updated)
	}
}

func TestUpdateUserContactInfoRejectsTooLong(t *testing.T) {
	repository := &fakeModerationRepository{user: userdomain.User{
		AppCode:          "lalu",
		UserID:           10001,
		ProfileCompleted: true,
	}}
	svc := New(repository)

	_, err := svc.UpdateUserContactInfo(appcode.WithContext(context.Background(), "lalu"), 10001, strings.Repeat("1", userdomain.ProfileContactInfoMaxRunes+1))
	if xerr.CodeOf(err) != xerr.InvalidArgument {
		t.Fatalf("too long contact_info must be rejected, err=%v", err)
	}
}

func TestUpdateUserWithdrawAddressPersistsUSDTTRC20Address(t *testing.T) {
	repository := &fakeModerationRepository{user: userdomain.User{
		AppCode:          "lalu",
		UserID:           10001,
		ProfileCompleted: true,
	}}
	svc := New(repository, WithClock(func() time.Time {
		return time.UnixMilli(1_700_000_000_000).UTC()
	}))

	updated, err := svc.UpdateUserWithdrawAddress(appcode.WithContext(context.Background(), "lalu"), 10001, " TCSTWxGagPpCS8RaZeBXMW9d69gUsK9QGb ")
	if err != nil {
		t.Fatalf("UpdateUserWithdrawAddress failed: %v", err)
	}

	if updated.WithdrawUSDTTRC20Address != "TCSTWxGagPpCS8RaZeBXMW9d69gUsK9QGb" {
		t.Fatalf("updated withdraw address mismatch: %+v", updated)
	}
	if repository.lastWithdrawAddressCommand.UpdatedAtMs != 1_700_000_000_000 {
		t.Fatalf("withdraw address command time mismatch: %+v", repository.lastWithdrawAddressCommand)
	}
}

func TestUpdateUserWithdrawAddressRejectsInvalidUSDTTRC20Address(t *testing.T) {
	repository := &fakeModerationRepository{user: userdomain.User{
		AppCode:          "lalu",
		UserID:           10001,
		ProfileCompleted: true,
	}}
	svc := New(repository)

	_, err := svc.UpdateUserWithdrawAddress(appcode.WithContext(context.Background(), "lalu"), 10001, "CSTWxGagPpCS8RaZeBXMW9d69gUsK9QGb")
	if xerr.CodeOf(err) != xerr.InvalidArgument {
		t.Fatalf("invalid withdraw address must be rejected, err=%v", err)
	}
	if repository.lastWithdrawAddressCommand.UserID != 0 {
		t.Fatalf("invalid withdraw address must not reach repository: %+v", repository.lastWithdrawAddressCommand)
	}
}
