package identity

import (
	"context"
	"errors"
	"strings"
	"time"

	"gorm.io/gorm"
)

func normalizeInviteCode(code *string) string {
	if code == nil {
		return ""
	}
	return strings.ToUpper(strings.TrimSpace(*code))
}

func (s *Service) bindInviteCode(ctx context.Context, user *User, inviteCode *string) error {
	code := normalizeInviteCode(inviteCode)
	if code == "" {
		return nil
	}

	agentUser, ok, err := s.resolveAgentFromCode(ctx, code)
	if err != nil || !ok {
		return err
	}
	if agentUser.ID == user.ID {
		return nil
	}

	user.BoundAgentID = &agentUser.ID
	if err := s.db.WithContext(ctx).
		Model(&User{}).
		Where("id = ?", user.ID).
		Update("bound_agent_id", agentUser.ID).Error; err != nil {
		return err
	}

	activeAgent, err := s.isActiveOrObservingAgent(ctx, agentUser.ID)
	if err != nil {
		return err
	}
	if activeAgent {
		if err := s.bindCustomerToAgent(ctx, agentUser.ID, user.ID); err != nil {
			return err
		}
	}

	return s.markAffiliateClickConverted(ctx, code, user.ID)
}

func (s *Service) resolveAgentFromCode(ctx context.Context, code string) (User, bool, error) {
	var user User
	err := s.db.WithContext(ctx).Where("invite_code = ?", code).First(&user).Error
	if err == nil {
		return user, true, nil
	}
	if !errors.Is(err, gorm.ErrRecordNotFound) {
		return User{}, false, err
	}

	var inviteCode UserInviteCode
	err = s.db.WithContext(ctx).Where("code = ?", code).First(&inviteCode).Error
	if err == nil {
		user, err = s.findUserByID(ctx, inviteCode.UserID)
		return user, err == nil, err
	}
	if !errors.Is(err, gorm.ErrRecordNotFound) {
		return User{}, false, err
	}

	var agent UserAgent
	err = s.db.WithContext(ctx).Where("invite_code = ?", code).First(&agent).Error
	if err == nil {
		user, err = s.findUserByID(ctx, agent.UserID)
		return user, err == nil, err
	}
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return User{}, false, nil
	}
	return User{}, false, err
}

func (s *Service) isActiveOrObservingAgent(ctx context.Context, userID int64) (bool, error) {
	var count int64
	err := s.db.WithContext(ctx).
		Model(&UserAgent{}).
		Where("user_id = ? AND status IN ?", userID, []string{"active", "observing"}).
		Count(&count).Error
	return count > 0, err
}

func (s *Service) bindCustomerToAgent(ctx context.Context, agentUserID int64, customerUserID int64) error {
	var existing AgentCustomer
	err := s.db.WithContext(ctx).
		Where("customer_user_id = ?", customerUserID).
		First(&existing).Error
	if err == nil {
		return nil
	}
	if !errors.Is(err, gorm.ErrRecordNotFound) {
		return err
	}

	now := time.Now().UTC()
	return s.db.WithContext(ctx).Create(&AgentCustomer{
		AgentUserID:    agentUserID,
		CustomerUserID: customerUserID,
		BindSource:     "invite_code",
		BoundAt:        now,
		Status:         "active",
	}).Error
}

func (s *Service) markAffiliateClickConverted(ctx context.Context, code string, userID int64) error {
	var click AffiliateClick
	err := s.db.WithContext(ctx).
		Where("aff_code = ? AND registered_user_id IS NULL", code).
		Order("id DESC").
		First(&click).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil
	}
	if err != nil {
		return err
	}
	now := time.Now().UTC()
	return s.db.WithContext(ctx).
		Model(&AffiliateClick{}).
		Where("id = ?", click.ID).
		Updates(map[string]any{
			"registered_user_id": userID,
			"converted_at":       now,
		}).Error
}
