package invite_test

import (
	"context"
	"testing"

	"hyapp/pkg/xerr"
	invitedomain "hyapp/services/user-service/internal/domain/invite"
	userdomain "hyapp/services/user-service/internal/domain/user"
	"hyapp/services/user-service/internal/testutil/mysqltest"
)

func TestInviteRelationCountersAndRechargeValidity(t *testing.T) {
	repo := mysqltest.NewRepository(t)
	ctx := context.Background()

	createUser(t, repo, 10001, "163001", 1000)
	createUser(t, repo, 10002, "163002", 1001)

	inviter, err := repo.GetUser(ctx, 10001)
	if err != nil {
		t.Fatalf("get inviter failed: %v", err)
	}
	if inviter.InviteOverview.MyInviteCode == "" || !inviter.InviteOverview.InviteEnabled || inviter.InviteOverview.ValidInviteThresholdCoin != 80000 {
		t.Fatalf("inviter should have active code and default policy threshold: %+v", inviter.InviteOverview)
	}

	invited, err := repo.CompleteOnboarding(ctx, userdomain.CompleteOnboardingCommand{
		AppCode:       "lalu",
		UserID:        10002,
		Username:      "alice",
		Avatar:        "https://cdn.example/a.png",
		Gender:        "female",
		Country:       "SG",
		InviteCode:    inviter.InviteOverview.MyInviteCode,
		RequestID:     "req-invite-bind",
		CompletedAtMs: 2000,
	})
	if err != nil {
		t.Fatalf("complete onboarding with invite failed: %v", err)
	}
	if !invited.InviteBinding.Bound || invited.InviteBinding.InviterUserID != 10001 {
		t.Fatalf("invite binding mismatch: %+v", invited.InviteBinding)
	}

	inviter, err = repo.GetUser(ctx, 10001)
	if err != nil {
		t.Fatalf("get inviter after bind failed: %v", err)
	}
	if inviter.InviteOverview.InviteCount != 1 || inviter.InviteOverview.ValidInviteCount != 0 {
		t.Fatalf("invite counter after bind mismatch: %+v", inviter.InviteOverview)
	}

	inviteRepo := repo.Repository.InviteRepository()
	first, err := inviteRepo.ApplyRechargeEvent(ctx, invitedomain.RechargeEvent{
		AppCode:            "lalu",
		EventID:            "evt-recharge-1",
		EventType:          invitedomain.RechargeEventWalletRecorded,
		TransactionID:      "tx-recharge-1",
		CommandID:          "cmd-recharge-1",
		InvitedUserID:      10002,
		AssetType:          "COIN",
		RechargeCoinAmount: 79999,
		RechargeType:       invitedomain.RechargeTypeCoinSellerTransfer,
		OccurredAtMs:       3000,
	})
	if err != nil || !first.Consumed || first.Valid {
		t.Fatalf("first recharge result mismatch: result=%+v err=%v", first, err)
	}
	inviter, _ = repo.GetUser(ctx, 10001)
	if inviter.InviteOverview.ValidInviteCount != 0 {
		t.Fatalf("79999 coin must not count as valid invite: %+v", inviter.InviteOverview)
	}

	second, err := inviteRepo.ApplyRechargeEvent(ctx, invitedomain.RechargeEvent{
		AppCode:            "lalu",
		EventID:            "evt-recharge-2",
		EventType:          invitedomain.RechargeEventWalletRecorded,
		TransactionID:      "tx-recharge-2",
		CommandID:          "cmd-recharge-2",
		InvitedUserID:      10002,
		AssetType:          "COIN",
		RechargeCoinAmount: 1,
		RechargeType:       invitedomain.RechargeTypeCoinSellerTransfer,
		OccurredAtMs:       4000,
	})
	if err != nil || !second.Consumed || !second.Valid {
		t.Fatalf("second recharge should make invite valid: result=%+v err=%v", second, err)
	}
	duplicate, err := inviteRepo.ApplyRechargeEvent(ctx, invitedomain.RechargeEvent{
		AppCode:            "lalu",
		EventID:            "evt-recharge-2",
		EventType:          invitedomain.RechargeEventWalletRecorded,
		TransactionID:      "tx-recharge-2",
		CommandID:          "cmd-recharge-2",
		InvitedUserID:      10002,
		AssetType:          "COIN",
		RechargeCoinAmount: 1,
		RechargeType:       invitedomain.RechargeTypeCoinSellerTransfer,
		OccurredAtMs:       4000,
	})
	if err != nil || !duplicate.Skipped || duplicate.Reason != "already_consumed" {
		t.Fatalf("duplicate recharge should be idempotently skipped: result=%+v err=%v", duplicate, err)
	}

	inviter, err = repo.GetUser(ctx, 10001)
	if err != nil {
		t.Fatalf("get inviter after valid failed: %v", err)
	}
	if inviter.InviteOverview.InviteCount != 1 || inviter.InviteOverview.ValidInviteCount != 1 {
		t.Fatalf("valid invite counter mismatch: %+v", inviter.InviteOverview)
	}
}

func TestCompleteOnboardingAllowsDifferentCountryInSameRegion(t *testing.T) {
	repo := mysqltest.NewRepository(t)
	ctx := context.Background()

	createCompletedUserWithRegion(t, repo, 10011, "163011", "AT", 3, 1000)
	createUser(t, repo, 10012, "163012", 1001)
	inviter, err := repo.GetUser(ctx, 10011)
	if err != nil {
		t.Fatalf("get inviter failed: %v", err)
	}

	invited, err := repo.CompleteOnboarding(ctx, userdomain.CompleteOnboardingCommand{
		AppCode:       "lalu",
		UserID:        10012,
		Username:      "same-region",
		Avatar:        "https://cdn.example/same-region.png",
		Gender:        "female",
		Country:       "SG",
		RegionID:      3,
		InviteCode:    inviter.InviteOverview.MyInviteCode,
		RequestID:     "req-same-region-onboarding",
		CompletedAtMs: 2000,
	})
	if err != nil {
		t.Fatalf("complete onboarding with same-region invite failed: %v", err)
	}
	if !invited.InviteBinding.Bound || invited.InviteBinding.InviterUserID != 10011 {
		t.Fatalf("same-region onboarding invite binding mismatch: %+v", invited.InviteBinding)
	}
}

func TestCompleteOnboardingRejectsDifferentRegionInvite(t *testing.T) {
	repo := mysqltest.NewRepository(t)
	ctx := context.Background()

	createCompletedUserWithRegion(t, repo, 10021, "163021", "AT", 3, 1000)
	createUser(t, repo, 10022, "163022", 1001)
	inviter, err := repo.GetUser(ctx, 10021)
	if err != nil {
		t.Fatalf("get inviter failed: %v", err)
	}

	_, err = repo.CompleteOnboarding(ctx, userdomain.CompleteOnboardingCommand{
		AppCode:       "lalu",
		UserID:        10022,
		Username:      "different-region",
		Avatar:        "https://cdn.example/different-region.png",
		Gender:        "female",
		Country:       "US",
		RegionID:      4,
		InviteCode:    inviter.InviteOverview.MyInviteCode,
		RequestID:     "req-different-region-onboarding",
		CompletedAtMs: 2000,
	})
	if xerr.CodeOf(err) != xerr.InvalidInviteCode {
		t.Fatalf("different-region invite must be invalid invite code, err=%v", err)
	}
}

func TestInviteRechargeValidityAcceptsAllRechargeSources(t *testing.T) {
	repo := mysqltest.NewRepository(t)
	ctx := context.Background()
	createUser(t, repo, 11001, "164001", 1000)
	inviter, err := repo.GetUser(ctx, 11001)
	if err != nil {
		t.Fatalf("get inviter failed: %v", err)
	}

	sources := []string{
		invitedomain.RechargeTypeCoinSellerTransfer,
		invitedomain.RechargeTypeGooglePlay,
		invitedomain.RechargeTypeGoogle,
		invitedomain.RechargeTypeMifaPay,
		invitedomain.RechargeTypeUSDTTRC20,
	}
	for index, source := range sources {
		invitedUserID := int64(11002 + index)
		createUser(t, repo, invitedUserID, "16400"+string(rune('2'+index)), int64(1100+index))
		_, err = repo.CompleteOnboarding(ctx, userdomain.CompleteOnboardingCommand{
			AppCode:       "lalu",
			UserID:        invitedUserID,
			Username:      "source-user",
			Avatar:        "https://cdn.example/source.png",
			Gender:        "female",
			Country:       "SG",
			InviteCode:    inviter.InviteOverview.MyInviteCode,
			RequestID:     "req-invite-source-" + source,
			CompletedAtMs: int64(2000 + index),
		})
		if err != nil {
			t.Fatalf("complete onboarding source %s failed: %v", source, err)
		}
		result, err := repo.Repository.InviteRepository().ApplyRechargeEvent(ctx, invitedomain.RechargeEvent{
			AppCode:            "lalu",
			EventID:            "evt-source-" + source,
			EventType:          invitedomain.RechargeEventWalletRecorded,
			TransactionID:      "tx-source-" + source,
			CommandID:          "cmd-source-" + source,
			InvitedUserID:      invitedUserID,
			AssetType:          "COIN",
			RechargeCoinAmount: 80000,
			RechargeType:       source,
			OccurredAtMs:       int64(3000 + index),
		})
		if err != nil || !result.Consumed || !result.Valid {
			t.Fatalf("source %s should count as valid recharge: result=%+v err=%v", source, result, err)
		}
	}

	inviter, err = repo.GetUser(ctx, 11001)
	if err != nil {
		t.Fatalf("get inviter after source recharge failed: %v", err)
	}
	if inviter.InviteOverview.ValidInviteCount != int64(len(sources)) {
		t.Fatalf("valid invite count mismatch: got=%d want=%d", inviter.InviteOverview.ValidInviteCount, len(sources))
	}
}

func createCompletedUserWithRegion(t *testing.T, repo *mysqltest.Repository, userID int64, displayUserID string, country string, regionID int64, nowMs int64) {
	t.Helper()
	if err := repo.CreateUserWithIdentity(context.Background(), userdomain.User{
		AppCode:                  "lalu",
		UserID:                   userID,
		DefaultDisplayUserID:     displayUserID,
		CurrentDisplayUserID:     displayUserID,
		CurrentDisplayUserIDKind: userdomain.DisplayUserIDKindDefault,
		Username:                 "completed-user",
		Country:                  country,
		RegionID:                 regionID,
		ProfileCompleted:         true,
		ProfileCompletedAtMs:     nowMs,
		Status:                   userdomain.StatusActive,
		OnboardingStatus:         userdomain.OnboardingStatusCompleted,
		CreatedAtMs:              nowMs,
		UpdatedAtMs:              nowMs,
	}, userdomain.Identity{
		AppCode:              "lalu",
		UserID:               userID,
		DisplayUserID:        displayUserID,
		DefaultDisplayUserID: displayUserID,
		DisplayUserIDKind:    userdomain.DisplayUserIDKindDefault,
		Status:               userdomain.DisplayUserIDStatusActive,
	}); err != nil {
		t.Fatalf("create completed user %d failed: %v", userID, err)
	}
}

func createUser(t *testing.T, repo *mysqltest.Repository, userID int64, displayUserID string, nowMs int64) {
	t.Helper()
	if err := repo.CreateUserWithIdentity(context.Background(), userdomain.User{
		AppCode:                  "lalu",
		UserID:                   userID,
		DefaultDisplayUserID:     displayUserID,
		CurrentDisplayUserID:     displayUserID,
		CurrentDisplayUserIDKind: userdomain.DisplayUserIDKindDefault,
		Status:                   userdomain.StatusActive,
		OnboardingStatus:         userdomain.OnboardingStatusProfileRequired,
		CreatedAtMs:              nowMs,
		UpdatedAtMs:              nowMs,
	}, userdomain.Identity{
		AppCode:              "lalu",
		UserID:               userID,
		DisplayUserID:        displayUserID,
		DefaultDisplayUserID: displayUserID,
		DisplayUserIDKind:    userdomain.DisplayUserIDKindDefault,
		Status:               userdomain.DisplayUserIDStatusActive,
	}); err != nil {
		t.Fatalf("create user %d failed: %v", userID, err)
	}
}
