package grpc_test

import (
	"context"
	"testing"
	"time"

	userv1 "hyapp.local/api/proto/user/v1"
	"hyapp/pkg/xerr"
	hostdomain "hyapp/services/user-service/internal/domain/host"
	userdomain "hyapp/services/user-service/internal/domain/user"
	authservice "hyapp/services/user-service/internal/service/auth"
	hostservice "hyapp/services/user-service/internal/service/host"
	"hyapp/services/user-service/internal/testutil/mysqltest"
	grpcserver "hyapp/services/user-service/internal/transport/grpc"
)

type grpcHostSequenceIDGenerator struct {
	next int64
}

func (g *grpcHostSequenceIDGenerator) NewInt64() int64 {
	id := g.next
	g.next++
	return id
}

func TestBDLeaderSelfInviteFlowThroughGRPCUsesSplitIdentityTables(t *testing.T) {
	ctx := context.Background()
	repository := mysqltest.NewRepository(t)
	repository.PutRegion(userdomain.Region{RegionID: 686, RegionCode: "BD-RPC", Name: "BD RPC Region"})
	seedActiveRPCUser(t, repository, 10001, 686)

	hostSvc := hostservice.New(repository.HostRepository(),
		hostservice.WithIDGenerator(&grpcHostSequenceIDGenerator{next: 700000}),
		hostservice.WithClock(func() time.Time { return time.UnixMilli(1700000000000) }),
	)
	server := grpcserver.NewServer(authservice.New(authservice.Config{}), newUserService(repository), hostSvc)
	meta := &userv1.RequestMeta{AppCode: "lalu", RequestId: "req-bd-grpc-flow"}

	leaderResp, err := server.CreateBDLeader(ctx, &userv1.CreateBDLeaderRequest{
		Meta:         meta,
		CommandId:    "rpc-create-leader-10001",
		AdminUserId:  90001,
		TargetUserId: 10001,
		Reason:       "grpc split identity flow",
	})
	if err != nil {
		t.Fatalf("CreateBDLeader RPC failed: %v", err)
	}
	if leaderResp.GetBdProfile().GetRole() != hostdomain.BDRoleLeader || leaderResp.GetBdProfile().GetStatus() != hostdomain.BDStatusActive {
		t.Fatalf("leader response mismatch: %+v", leaderResp.GetBdProfile())
	}

	if _, err := server.GetBDProfile(ctx, &userv1.GetBDProfileRequest{Meta: meta, UserId: 10001, Role: hostdomain.BDRoleBD}); xerr.ReasonFromGRPC(err) != xerr.NotFound {
		t.Fatalf("leader-only user must not have ordinary bd before self invite: err=%v", err)
	}

	inviteResp, err := server.InviteBD(ctx, &userv1.InviteBDRequest{
		Meta:          meta,
		CommandId:     "rpc-self-invite-bd-10001",
		InviterUserId: 10001,
		TargetUserId:  10001,
	})
	if err != nil {
		t.Fatalf("InviteBD self RPC failed: %v", err)
	}
	invitation := inviteResp.GetInvitation()
	if invitation.GetStatus() != hostdomain.InvitationStatusPending || invitation.GetTargetUserId() != 10001 || invitation.GetParentLeaderUserId() != 10001 {
		t.Fatalf("self invite response mismatch: %+v", invitation)
	}
	retryInviteResp, err := server.InviteBD(ctx, &userv1.InviteBDRequest{
		Meta:          meta,
		CommandId:     "rpc-self-invite-bd-10001",
		InviterUserId: 10001,
		TargetUserId:  10001,
	})
	if err != nil {
		t.Fatalf("InviteBD retry RPC failed: %v", err)
	}
	if retryInviteResp.GetInvitation().GetInvitationId() != invitation.GetInvitationId() {
		t.Fatalf("self invite retry must replay same invitation: first=%+v retry=%+v", invitation, retryInviteResp.GetInvitation())
	}

	if _, err := server.GetBDProfile(ctx, &userv1.GetBDProfileRequest{Meta: meta, UserId: 10001, Role: hostdomain.BDRoleBD}); xerr.ReasonFromGRPC(err) != xerr.NotFound {
		t.Fatalf("pending self invite must not create ordinary bd before accept: err=%v", err)
	}
	listResp, err := server.ListRoleInvitations(ctx, &userv1.ListRoleInvitationsRequest{
		Meta:         meta,
		TargetUserId: 10001,
		Status:       hostdomain.InvitationStatusPending,
		PageSize:     20,
	})
	if err != nil {
		t.Fatalf("ListRoleInvitations RPC failed: %v", err)
	}
	if len(listResp.GetInvitations()) != 1 || listResp.GetInvitations()[0].GetInvitationId() != invitation.GetInvitationId() {
		t.Fatalf("pending invitation list mismatch: %+v", listResp.GetInvitations())
	}
	processResp, err := server.ProcessRoleInvitation(ctx, &userv1.ProcessRoleInvitationRequest{
		Meta:         meta,
		CommandId:    "rpc-accept-self-bd-10001",
		ActorUserId:  10001,
		InvitationId: invitation.GetInvitationId(),
		Action:       hostdomain.InvitationActionAccept,
		Reason:       "accept self bd",
	})
	if err != nil {
		t.Fatalf("ProcessRoleInvitation accept RPC failed: %v", err)
	}
	if processResp.GetInvitation().GetStatus() != hostdomain.InvitationStatusAccepted || processResp.GetBdProfile().GetRole() != hostdomain.BDRoleBD {
		t.Fatalf("accepted self invite response mismatch: %+v bd=%+v", processResp.GetInvitation(), processResp.GetBdProfile())
	}

	bdResp, err := server.GetBDProfile(ctx, &userv1.GetBDProfileRequest{Meta: meta, UserId: 10001, Role: hostdomain.BDRoleBD})
	if err != nil {
		t.Fatalf("GetBDProfile role=bd RPC failed after accepting self invite: %v", err)
	}
	if bdResp.GetBdProfile().GetRole() != hostdomain.BDRoleBD || bdResp.GetBdProfile().GetParentLeaderUserId() != 10001 {
		t.Fatalf("ordinary bd profile mismatch: %+v", bdResp.GetBdProfile())
	}
	leaderCheckResp, err := server.GetBDProfile(ctx, &userv1.GetBDProfileRequest{Meta: meta, UserId: 10001, Role: hostdomain.BDRoleLeader})
	if err != nil {
		t.Fatalf("GetBDProfile role=bd_leader RPC failed: %v", err)
	}
	if leaderCheckResp.GetBdProfile().GetRole() != hostdomain.BDRoleLeader || leaderCheckResp.GetBdProfile().GetStatus() != hostdomain.BDStatusActive {
		t.Fatalf("leader profile changed unexpectedly: %+v", leaderCheckResp.GetBdProfile())
	}

	summaryResp, err := server.GetUserRoleSummary(ctx, &userv1.GetUserRoleSummaryRequest{Meta: meta, UserId: 10001})
	if err != nil {
		t.Fatalf("GetUserRoleSummary RPC failed: %v", err)
	}
	if !summaryResp.GetSummary().GetIsBd() || !summaryResp.GetSummary().GetIsBdLeader() {
		t.Fatalf("summary must expose both independent identities: %+v", summaryResp.GetSummary())
	}

	disabledBDResp, err := server.SetBDStatus(ctx, &userv1.SetBDStatusRequest{
		Meta:         meta,
		CommandId:    "rpc-disable-bd-10001",
		AdminUserId:  90001,
		TargetUserId: 10001,
		Role:         hostdomain.BDRoleBD,
		Status:       hostdomain.BDStatusDisabled,
		Reason:       "disable ordinary bd only",
	})
	if err != nil {
		t.Fatalf("SetBDStatus role=bd RPC failed: %v", err)
	}
	if disabledBDResp.GetBdProfile().GetRole() != hostdomain.BDRoleBD || disabledBDResp.GetBdProfile().GetStatus() != hostdomain.BDStatusDisabled {
		t.Fatalf("disabled ordinary bd response mismatch: %+v", disabledBDResp.GetBdProfile())
	}
	leaderStillActiveResp, err := server.GetBDProfile(ctx, &userv1.GetBDProfileRequest{Meta: meta, UserId: 10001, Role: hostdomain.BDRoleLeader})
	if err != nil {
		t.Fatalf("GetBDProfile role=bd_leader after bd disable failed: %v", err)
	}
	if leaderStillActiveResp.GetBdProfile().GetStatus() != hostdomain.BDStatusActive {
		t.Fatalf("disabling ordinary bd must not affect leader: %+v", leaderStillActiveResp.GetBdProfile())
	}

	disabledLeaderResp, err := server.SetBDStatus(ctx, &userv1.SetBDStatusRequest{
		Meta:         meta,
		CommandId:    "rpc-disable-leader-10001",
		AdminUserId:  90001,
		TargetUserId: 10001,
		Role:         hostdomain.BDRoleLeader,
		Status:       hostdomain.BDStatusDisabled,
		Reason:       "disable leader only",
	})
	if err != nil {
		t.Fatalf("SetBDStatus role=bd_leader RPC failed: %v", err)
	}
	if disabledLeaderResp.GetBdProfile().GetRole() != hostdomain.BDRoleLeader || disabledLeaderResp.GetBdProfile().GetStatus() != hostdomain.BDStatusDisabled {
		t.Fatalf("disabled leader response mismatch: %+v", disabledLeaderResp.GetBdProfile())
	}

	var bdRows int
	var leaderRows int
	if err := repository.RawDB().QueryRowContext(ctx, `SELECT COUNT(1) FROM bd_profiles WHERE app_code = 'lalu' AND user_id = 10001 AND role = 'bd'`).Scan(&bdRows); err != nil {
		t.Fatalf("count ordinary bd rows failed: %v", err)
	}
	if err := repository.RawDB().QueryRowContext(ctx, `SELECT COUNT(1) FROM bd_leader_profiles WHERE app_code = 'lalu' AND user_id = 10001`).Scan(&leaderRows); err != nil {
		t.Fatalf("count leader rows failed: %v", err)
	}
	if bdRows != 1 || leaderRows != 1 {
		t.Fatalf("split identity rows mismatch: bd_rows=%d leader_rows=%d", bdRows, leaderRows)
	}
}

func TestBatchGetHostProfilesThroughGRPCReturnsProfilesByUserID(t *testing.T) {
	ctx := context.Background()
	repository := mysqltest.NewRepository(t)
	repository.PutRegion(userdomain.Region{RegionID: 710, RegionCode: "HOST-BATCH-1", Name: "Host Batch One"})
	repository.PutRegion(userdomain.Region{RegionID: 720, RegionCode: "HOST-BATCH-2", Name: "Host Batch Two"})
	seedActiveRPCUser(t, repository, 11001, 710)
	seedActiveRPCUser(t, repository, 11002, 720)
	seedActiveRPCUser(t, repository, 11003, 720)
	repository.PutHostProfile(hostdomain.HostProfile{UserID: 11001, Status: hostdomain.HostStatusActive, Source: "grpc-batch"})
	repository.PutHostProfile(hostdomain.HostProfile{UserID: 11002, Status: hostdomain.HostStatusDisabled, Source: "grpc-batch"})
	hostSvc := hostservice.New(repository.HostRepository())
	server := grpcserver.NewServer(authservice.New(authservice.Config{}), newUserService(repository), hostSvc)
	meta := &userv1.RequestMeta{AppCode: "lalu", RequestId: "req-host-batch"}

	resp, err := server.BatchGetHostProfiles(ctx, &userv1.BatchGetHostProfilesRequest{Meta: meta, UserIds: []int64{11001, 11002, 11003}})
	if err != nil {
		t.Fatalf("BatchGetHostProfiles RPC failed: %v", err)
	}
	profiles := resp.GetHostProfiles()
	if len(profiles) != 2 {
		t.Fatalf("batch host profiles should omit missing rows, got %+v", profiles)
	}
	if profiles[11001].GetStatus() != hostdomain.HostStatusActive || profiles[11001].GetRegionId() != 710 {
		t.Fatalf("active host profile mismatch: %+v", profiles[11001])
	}
	if profiles[11002].GetStatus() != hostdomain.HostStatusDisabled || profiles[11002].GetRegionId() != 720 {
		t.Fatalf("disabled host profile should be returned for caller-side policy filtering: %+v", profiles[11002])
	}
	if profiles[11003] != nil {
		t.Fatalf("missing host profile must not be returned as placeholder: %+v", profiles[11003])
	}

	if _, err := server.BatchGetHostProfiles(ctx, &userv1.BatchGetHostProfilesRequest{Meta: meta, UserIds: []int64{11001, 0}}); xerr.ReasonFromGRPC(err) != xerr.InvalidArgument {
		t.Fatalf("invalid batch user id should be converted to INVALID_ARGUMENT: %v", err)
	}
}

func seedActiveRPCUser(t *testing.T, repository *mysqltest.Repository, userID int64, regionID int64) {
	t.Helper()
	repository.PutUser(userdomain.User{
		UserID:                   userID,
		DefaultDisplayUserID:     "910001",
		CurrentDisplayUserID:     "910001",
		CurrentDisplayUserIDKind: userdomain.DisplayUserIDKindDefault,
		Country:                  "CN",
		RegionID:                 regionID,
		ProfileCompleted:         true,
		ProfileCompletedAtMs:     1,
		OnboardingStatus:         userdomain.OnboardingStatusCompleted,
		Status:                   userdomain.StatusActive,
	})
}
