package user

import (
	"context"
	"database/sql"
	"fmt"
	"strings"

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	invitedomain "hyapp/services/user-service/internal/domain/invite"
	userdomain "hyapp/services/user-service/internal/domain/user"
	invitestorage "hyapp/services/user-service/internal/storage/mysql/invite"
)

// FindInviteReferrerByCode 按 active 邀请码读取归属用户，搜索阶段只做只读解析。
func (r *Repository) FindInviteReferrerByCode(ctx context.Context, appCode string, inviteCode string) (userdomain.User, error) {
	if r == nil || r.db == nil {
		return userdomain.User{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	appCode = appcode.Normalize(appCode)
	inviteCode = invitedomain.NormalizeCode(inviteCode)
	if !invitedomain.ValidCode(inviteCode) {
		return userdomain.User{}, xerr.New(xerr.InvalidInviteCode, "invalid invite code")
	}

	row := r.db.QueryRowContext(ctx, `
		SELECT `+userSelectColumns+`
		FROM users
		WHERE users.app_code = ?
		  AND EXISTS (
		    SELECT 1
		    FROM user_invite_codes codes
		    WHERE codes.app_code = users.app_code
		      AND codes.owner_user_id = users.user_id
		      AND codes.code = ?
		      AND codes.status = ?
		    LIMIT 1
		  )
		LIMIT 1
	`, appCode, inviteCode, invitedomain.CodeStatusActive)
	inviter, err := ScanUser(row)
	if err == sql.ErrNoRows {
		return userdomain.User{}, xerr.New(xerr.NotFound, "inviter not found")
	}
	if err != nil {
		return userdomain.User{}, err
	}
	return inviter, nil
}

// BindInviteReferrer 在资料完成后绑定当前用户的邀请人。
// 这里必须锁双方 users 行并在同一事务内复核区域和账号可用性，不能只信 H5 先前搜索结果。
func (r *Repository) BindInviteReferrer(ctx context.Context, command userdomain.BindInviteReferrerCommand) (userdomain.User, error) {
	if r == nil || r.db == nil {
		return userdomain.User{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	command.AppCode = appcode.Normalize(command.AppCode)
	command.InviteCode = invitedomain.NormalizeCode(command.InviteCode)
	command.RequestID = strings.TrimSpace(command.RequestID)
	if command.InvitedUserID <= 0 || !invitedomain.ValidCode(command.InviteCode) {
		return userdomain.User{}, xerr.New(xerr.InvalidInviteCode, "invalid invite code")
	}

	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return userdomain.User{}, err
	}
	defer tx.Rollback()

	inviterUserID, err := inviteReferrerOwnerUserID(ctx, tx, command.AppCode, command.InviteCode)
	if err != nil {
		return userdomain.User{}, err
	}
	if command.InvitedUserID == inviterUserID {
		return userdomain.User{}, xerr.New(xerr.InvalidInviteCode, "invalid invite code")
	}

	invited, inviter, err := lockInviteReferrerUsers(ctx, tx, command.AppCode, command.InvitedUserID, inviterUserID)
	if err != nil {
		return userdomain.User{}, err
	}
	if err := validateLockedInviteReferrerPair(invited, inviter); err != nil {
		return userdomain.User{}, err
	}

	if existing, exists, err := inviteRelationForInvited(ctx, tx, command.AppCode, command.InvitedUserID); err != nil {
		return userdomain.User{}, err
	} else if exists {
		if invitedomain.NormalizeCode(existing.InviteCode) != command.InviteCode {
			// 邀请归因是一次性事实；已绑定其他邀请人时不能被 H5 改写。
			return userdomain.User{}, xerr.New(xerr.Conflict, "invite referrer already bound")
		}
		if err := fillUserInviteCodeSnapshot(ctx, tx, command, existing.InviteCode); err != nil {
			return userdomain.User{}, err
		}
		if err := tx.Commit(); err != nil {
			return userdomain.User{}, err
		}
		updated, err := r.GetUser(ctx, command.InvitedUserID)
		updated.InviteBinding = invitedomain.Binding{InviteCode: existing.InviteCode, InviterUserID: existing.InviterUserID}
		return updated, err
	}

	binding, err := invitestorage.BindRelation(ctx, tx, invitedomain.BindCommand{
		AppCode:         command.AppCode,
		InvitedUserID:   command.InvitedUserID,
		InvitedRegionID: invited.RegionID,
		InviteCode:      command.InviteCode,
		Source:          "bind_invite_referrer",
		RequestID:       command.RequestID,
		BoundAtMs:       command.BoundAtMs,
	})
	if err != nil {
		return userdomain.User{}, err
	}
	if binding.InviterUserID != inviter.UserID || invitedomain.NormalizeCode(binding.InviteCode) != command.InviteCode {
		// 并发下如果另一个事务先绑定了不同邀请人，保持一次性归因语义并让调用方刷新状态。
		return userdomain.User{}, xerr.New(xerr.Conflict, "invite referrer already bound")
	}
	if err := fillUserInviteCodeSnapshot(ctx, tx, command, binding.InviteCode); err != nil {
		return userdomain.User{}, err
	}
	if err := tx.Commit(); err != nil {
		return userdomain.User{}, err
	}

	updated, err := r.GetUser(ctx, command.InvitedUserID)
	updated.InviteBinding = binding
	return updated, err
}

func inviteReferrerOwnerUserID(ctx context.Context, tx *sql.Tx, appCode string, inviteCode string) (int64, error) {
	var ownerUserID int64
	err := tx.QueryRowContext(ctx, `
		SELECT owner_user_id
		FROM user_invite_codes
		WHERE app_code = ? AND code = ? AND status = ?
		LIMIT 1
	`, appCode, inviteCode, invitedomain.CodeStatusActive).Scan(&ownerUserID)
	if err == sql.ErrNoRows {
		return 0, xerr.New(xerr.InvalidInviteCode, "invalid invite code")
	}
	if err != nil {
		return 0, err
	}
	return ownerUserID, nil
}

func lockInviteReferrerUsers(ctx context.Context, tx *sql.Tx, appCode string, invitedUserID int64, inviterUserID int64) (userdomain.User, userdomain.User, error) {
	rows, err := tx.QueryContext(ctx, fmt.Sprintf(`
		SELECT %s
		FROM users
		WHERE app_code = ? AND user_id IN (?, ?)
		ORDER BY user_id ASC
		FOR UPDATE
	`, userSelectColumns), appCode, invitedUserID, inviterUserID)
	if err != nil {
		return userdomain.User{}, userdomain.User{}, err
	}
	defer rows.Close()

	users := make(map[int64]userdomain.User, 2)
	for rows.Next() {
		user, err := ScanUser(rows)
		if err != nil {
			return userdomain.User{}, userdomain.User{}, err
		}
		users[user.UserID] = user
	}
	if err := rows.Err(); err != nil {
		return userdomain.User{}, userdomain.User{}, err
	}
	invited, invitedOK := users[invitedUserID]
	inviter, inviterOK := users[inviterUserID]
	if !invitedOK || !inviterOK {
		return userdomain.User{}, userdomain.User{}, xerr.New(xerr.NotFound, "user not found")
	}
	return invited, inviter, nil
}

func validateLockedInviteReferrerPair(invited userdomain.User, inviter userdomain.User) error {
	if !invited.ProfileCompleted {
		return xerr.New(xerr.ProfileRequired, "profile required")
	}
	if !invited.CanLogin() {
		return xerr.New(xerr.PermissionDenied, "permission denied")
	}
	if !inviter.ProfileCompleted || !inviter.CanLogin() {
		return xerr.New(xerr.NotFound, "inviter not found")
	}
	if invited.RegionID != inviter.RegionID {
		// 邀请绑定按区域隔离；跨区域统一隐藏邀请人存在性，避免直接 POST 邀请码绕过搜索态校验。
		return xerr.New(xerr.NotFound, "inviter not found")
	}
	return nil
}

func inviteRelationForInvited(ctx context.Context, tx *sql.Tx, appCode string, invitedUserID int64) (invitedomain.Binding, bool, error) {
	var binding invitedomain.Binding
	err := tx.QueryRowContext(ctx, `
		SELECT inviter_user_id, invite_code
		FROM user_invite_relations
		WHERE app_code = ? AND invited_user_id = ?
		LIMIT 1
		FOR UPDATE
	`, appCode, invitedUserID).Scan(&binding.InviterUserID, &binding.InviteCode)
	if err == sql.ErrNoRows {
		return invitedomain.Binding{}, false, nil
	}
	if err != nil {
		return invitedomain.Binding{}, false, err
	}
	return binding, true, nil
}

func fillUserInviteCodeSnapshot(ctx context.Context, tx *sql.Tx, command userdomain.BindInviteReferrerCommand, inviteCode string) error {
	inviteCode = invitedomain.NormalizeCode(inviteCode)
	if inviteCode == "" {
		return nil
	}
	_, err := tx.ExecContext(ctx, `
		UPDATE users
		SET invite_code = CASE WHEN COALESCE(invite_code, '') = '' THEN ? ELSE invite_code END,
		    updated_at_ms = GREATEST(updated_at_ms, ?)
		WHERE app_code = ? AND user_id = ?
	`, inviteCode, command.BoundAtMs, command.AppCode, command.InvitedUserID)
	return err
}
