package user

import (
	"context"
	"errors"
	"testing"
	"time"

	roomv1 "hyapp.local/api/proto/room/v1"
	"hyapp/pkg/appcode"
	invitedomain "hyapp/services/user-service/internal/domain/invite"
	userdomain "hyapp/services/user-service/internal/domain/user"
)

func TestSetUserStatusBanRevokesSessionsAndKicksRealtimeSurfaces(t *testing.T) {
	repository := &fakeModerationRepository{
		user: userdomain.User{
			AppCode: "lalu",
			UserID:  10001,
			Status:  userdomain.StatusBanned,
		},
		sessionIDs: []string{"sess_a", "sess_b"},
	}
	denylist := &fakeSessionDenylist{}
	im := &fakeIMLoginKicker{}
	room := &fakeRoomEvictor{resp: &roomv1.SystemEvictUserResponse{
		HadCurrentRoom: true,
		Result:         &roomv1.CommandResult{Applied: true, RoomVersion: 7},
		RoomId:         "room_1001",
		RtcKicked:      true,
	}}
	svc := New(repository,
		WithModerationRepository(repository),
		WithSessionDenylist(denylist, time.Minute),
		WithIMLoginKicker(im),
		WithRoomEvictor(room),
		WithClock(func() time.Time { return time.UnixMilli(1_700_000_000_000).UTC() }),
	)

	result, err := svc.SetUserStatus(appcode.WithContext(context.Background(), "lalu"), UserStatusCommand{
		AppCode:        "lalu",
		TargetUserID:   10001,
		Status:         userdomain.StatusBanned,
		OperatorType:   OperatorTypeAdmin,
		OperatorUserID: 9001,
		RequestID:      "req-ban",
	})
	if err != nil {
		t.Fatalf("SetUserStatus failed: %v", err)
	}

	if repository.lastCommand.Status != userdomain.StatusBanned || repository.lastCommand.OperatorType != OperatorTypeAdmin || repository.lastCommand.NowMs != 1_700_000_000_000 {
		t.Fatalf("unexpected repository command: %+v", repository.lastCommand)
	}
	if len(denylist.sessions) != 2 || denylist.sessions[0] != "sess_a" || denylist.sessions[1] != "sess_b" {
		t.Fatalf("denylist sessions mismatch: %+v", denylist.sessions)
	}
	if im.userID != 10001 {
		t.Fatalf("im kick target mismatch: %d", im.userID)
	}
	if room.req.GetTargetUserId() != 10001 || !room.req.GetBanFromRoom() || room.req.GetMeta().GetAppCode() != "lalu" {
		t.Fatalf("room eviction request mismatch: %+v", room.req)
	}
	if !result.AccessTokenRevoked || result.AccessTokenError != "" {
		t.Fatalf("access token revoke result mismatch: %+v", result)
	}
	if !result.IMKicked || !result.RoomEvicted || !result.RTCKicked || result.RoomID != "room_1001" {
		t.Fatalf("unexpected moderation result: %+v", result)
	}
}

func TestSetUserStatusSurfacesAccessDenylistError(t *testing.T) {
	repository := &fakeModerationRepository{
		user: userdomain.User{
			AppCode: "lalu",
			UserID:  10001,
			Status:  userdomain.StatusBanned,
		},
		sessionIDs: []string{"sess_a"},
	}
	denylist := &fakeSessionDenylist{err: errors.New("redis unavailable")}
	svc := New(repository,
		WithModerationRepository(repository),
		WithSessionDenylist(denylist, time.Minute),
	)

	result, err := svc.SetUserStatus(appcode.WithContext(context.Background(), "lalu"), UserStatusCommand{
		AppCode:      "lalu",
		TargetUserID: 10001,
		Status:       userdomain.StatusBanned,
		RequestID:    "req-ban",
	})
	if err != nil {
		t.Fatalf("SetUserStatus failed: %v", err)
	}

	if result.AccessTokenRevoked || result.AccessTokenError != "redis unavailable" {
		t.Fatalf("denylist error must be surfaced for retry: %+v", result)
	}
	if result.RevokedSessionCount != 1 {
		t.Fatalf("database revocation fact mismatch: %+v", result)
	}
}

type fakeModerationRepository struct {
	user               userdomain.User
	sessionIDs         []string
	countrySessionIDs  []string
	lastCommand        UserStatusCommand
	lastCountryCommand userdomain.CountryChangeCommand
	countryChangeCalls int
	lastReport         userdomain.Report
}

func (r *fakeModerationRepository) SetUserStatus(_ context.Context, command UserStatusCommand) (UserStatusPersistenceResult, error) {
	r.lastCommand = command
	r.user.Status = command.Status
	return UserStatusPersistenceResult{User: r.user, RevokedSessionIDs: append([]string{}, r.sessionIDs...)}, nil
}

func (r *fakeModerationRepository) GetInviteAttribution(context.Context, int64) (invitedomain.Attribution, error) {
	return invitedomain.Attribution{}, nil
}

func (r *fakeModerationRepository) CreateManagerUserBlock(_ context.Context, command ManagerUserBlockCommand, target userdomain.User) (ManagerUserBlock, error) {
	return ManagerUserBlock{
		BlockID:             "block-1",
		ManagerUserID:       command.ManagerUserID,
		TargetUserID:        command.TargetUserID,
		TargetDisplayUserID: target.CurrentDisplayUserID,
		TargetUsername:      target.Username,
		TargetAvatar:        target.Avatar,
		Country:             target.Country,
		BlockedUntilMS:      command.BlockedUntilMS,
		Status:              ManagerUserBlockStatusActive,
		Reason:              command.Reason,
	}, nil
}

func (r *fakeModerationRepository) ListManagerUserBlocks(context.Context, int64, string, int32) ([]ManagerUserBlock, error) {
	return nil, nil
}

func (r *fakeModerationRepository) ReleaseManagerUserBlock(context.Context, ReleaseManagerUserBlockCommand) (ManagerUserBlockRelease, error) {
	return ManagerUserBlockRelease{}, nil
}

func (r *fakeModerationRepository) ExpireManagerUserBlocks(context.Context, int64, int32) ([]ManagerUserBlockRelease, error) {
	return nil, nil
}

func (r *fakeModerationRepository) GetUser(context.Context, int64) (userdomain.User, error) {
	return r.user, nil
}

func (r *fakeModerationRepository) GetMyProfileStats(context.Context, int64) (userdomain.ProfileStats, error) {
	return userdomain.ProfileStats{}, nil
}

func (r *fakeModerationRepository) RecordProfileVisit(context.Context, int64, int64, int64) (bool, userdomain.ProfileStats, error) {
	return false, userdomain.ProfileStats{}, nil
}

func (r *fakeModerationRepository) ListProfileVisitors(context.Context, int64, int32, int32) ([]userdomain.ProfileVisitRecord, int64, error) {
	return nil, 0, nil
}

func (r *fakeModerationRepository) FollowUser(context.Context, int64, int64, int64) (userdomain.ProfileStats, error) {
	return userdomain.ProfileStats{}, nil
}

func (r *fakeModerationRepository) UnfollowUser(context.Context, int64, int64, int64) (userdomain.ProfileStats, error) {
	return userdomain.ProfileStats{}, nil
}

func (r *fakeModerationRepository) ListFollowing(context.Context, int64, int32, int32, int64, int64) ([]userdomain.FollowRecord, int64, error) {
	return nil, 0, nil
}

func (r *fakeModerationRepository) ApplyFriend(context.Context, int64, int64, int64) (userdomain.FriendApplication, bool, error) {
	return userdomain.FriendApplication{}, false, nil
}

func (r *fakeModerationRepository) AcceptFriendApplication(context.Context, int64, int64, int64) (userdomain.FriendRecord, error) {
	return userdomain.FriendRecord{}, nil
}

func (r *fakeModerationRepository) DeleteFriend(context.Context, int64, int64, int64) (bool, error) {
	return false, nil
}

func (r *fakeModerationRepository) ListFriends(context.Context, int64, int32, int32, int64, int64) ([]userdomain.FriendRecord, int64, error) {
	return nil, 0, nil
}

func (r *fakeModerationRepository) ListFriendApplications(context.Context, int64, string, int32, int32) ([]userdomain.FriendApplication, int64, error) {
	return nil, 0, nil
}

func (r *fakeModerationRepository) SubmitReport(_ context.Context, report userdomain.Report) (userdomain.Report, error) {
	r.lastReport = report
	return report, nil
}

func (r *fakeModerationRepository) BusinessUserLookup(context.Context, string, bool, int32) ([]userdomain.BusinessUserLookupItem, error) {
	return nil, nil
}

func (r *fakeModerationRepository) BatchGetUsers(context.Context, []int64) (map[int64]userdomain.User, error) {
	return nil, nil
}

func (r *fakeModerationRepository) ListUserIDs(context.Context, userdomain.UserIDPageFilter) ([]int64, error) {
	return nil, nil
}

func (r *fakeModerationRepository) CreateUserWithIdentity(context.Context, userdomain.User, userdomain.Identity) error {
	return nil
}

func (r *fakeModerationRepository) UpdateUserProfile(context.Context, userdomain.ProfileUpdateCommand) (userdomain.User, error) {
	return r.user, nil
}

func (r *fakeModerationRepository) CompleteOnboarding(context.Context, userdomain.CompleteOnboardingCommand) (userdomain.User, error) {
	return r.user, nil
}

func (r *fakeModerationRepository) ChangeUserCountry(_ context.Context, command userdomain.CountryChangeCommand) (userdomain.User, int64, []string, error) {
	r.lastCountryCommand = command
	r.countryChangeCalls++
	r.user.Country = command.NewCountry
	r.user.RegionID = command.NewRegionID
	r.user.UpdatedAtMs = command.ChangedAtMs
	return r.user, 0, append([]string{}, r.countrySessionIDs...), nil
}

type fakeSessionDenylist struct {
	sessions []string
	reasons  []string
	err      error
}

func (d *fakeSessionDenylist) SetRevokedSession(_ context.Context, _ string, sessionID string, reason string, _ time.Duration) error {
	d.sessions = append(d.sessions, sessionID)
	d.reasons = append(d.reasons, reason)
	return d.err
}

type fakeIMLoginKicker struct {
	userID int64
}

func (k *fakeIMLoginKicker) KickUser(_ context.Context, userID int64) error {
	k.userID = userID
	return nil
}

type fakeRoomEvictor struct {
	req  *roomv1.SystemEvictUserRequest
	resp *roomv1.SystemEvictUserResponse
}

func (e *fakeRoomEvictor) SystemEvictUser(_ context.Context, req *roomv1.SystemEvictUserRequest) (*roomv1.SystemEvictUserResponse, error) {
	e.req = req
	return e.resp, nil
}
