package repository

import (
	"context"
	"strings"

	"gorm.io/gorm"

	"security-center-backend/internal/model"
)

type UserRepository interface {
	FindByID(ctx context.Context, id uint64) (*model.User, error)
	FindByAuth0ID(ctx context.Context, auth0UserID string) (*model.User, error)
	FindByExternalID(ctx context.Context, externalID string) (*model.User, error)
	FindByEmail(ctx context.Context, email string) (*model.User, error)
	FindByEmails(ctx context.Context, emails []string) ([]model.User, error)
	CreateOrUpdate(ctx context.Context, user *model.User) error
	List(ctx context.Context, filter UserFilter, page, pageSize int) ([]model.User, int64, error)
	AssignRoles(ctx context.Context, userID uint64, roleIDs []uint64) error
	RemoveRole(ctx context.Context, userID uint64, roleID uint64) error
	GetUserRoles(ctx context.Context, userID uint64) ([]model.Role, error)
	GetUserPermissions(ctx context.Context, userID uint64) ([]model.Permission, error)
	UpdateDefaultRole(ctx context.Context, userID uint64, roleID *uint64) error
}

type GormUserRepository struct {
	db *gorm.DB
}

func NewUserRepository(db *gorm.DB) UserRepository {
	return &GormUserRepository{db: db}
}

func (r *GormUserRepository) FindByID(ctx context.Context, id uint64) (*model.User, error) {
	var user model.User
	if err := r.db.WithContext(ctx).Preload("Roles.Permissions").Preload("DefaultRole").Preload("Employee").First(&user, id).Error; err != nil {
		return nil, err
	}
	return &user, nil
}

func (r *GormUserRepository) FindByAuth0ID(ctx context.Context, auth0UserID string) (*model.User, error) {
	var user model.User
	if err := r.db.WithContext(ctx).
		Preload("Roles.Permissions").
		Preload("DefaultRole").
		Preload("Employee").
		Where("auth0_user_id = ?", auth0UserID).
		First(&user).Error; err != nil {
		return nil, err
	}
	return &user, nil
}

func (r *GormUserRepository) FindByExternalID(ctx context.Context, externalID string) (*model.User, error) {
	var user model.User
	if err := r.db.WithContext(ctx).
		Preload("Roles.Permissions").
		Preload("DefaultRole").
		Preload("Employee").
		Where("external_id = ?", externalID).
		First(&user).Error; err != nil {
		return nil, err
	}
	return &user, nil
}

func (r *GormUserRepository) FindByEmail(ctx context.Context, email string) (*model.User, error) {
	normalizedEmail := strings.ToLower(strings.TrimSpace(email))
	var user model.User
	if err := r.db.WithContext(ctx).
		Preload("Roles.Permissions").
		Preload("DefaultRole").
		Preload("Employee").
		Where("LOWER(TRIM(email)) = ?", normalizedEmail).
		First(&user).Error; err != nil {
		return nil, err
	}
	return &user, nil
}

func (r *GormUserRepository) FindByEmails(ctx context.Context, emails []string) ([]model.User, error) {
	users := make([]model.User, 0)
	if len(emails) == 0 {
		return users, nil
	}
	normalizedEmails := make([]string, 0, len(emails))
	seen := make(map[string]struct{}, len(emails))
	for _, email := range emails {
		normalized := strings.ToLower(strings.TrimSpace(email))
		if normalized == "" {
			continue
		}
		if _, exists := seen[normalized]; exists {
			continue
		}
		seen[normalized] = struct{}{}
		normalizedEmails = append(normalizedEmails, normalized)
	}
	if len(normalizedEmails) == 0 {
		return users, nil
	}
	if err := r.db.WithContext(ctx).
		Preload("Roles.Permissions").
		Preload("DefaultRole").
		Preload("Employee").
		Where("LOWER(TRIM(email)) IN ?", normalizedEmails).
		Find(&users).Error; err != nil {
		return nil, err
	}
	return users, nil
}

func (r *GormUserRepository) CreateOrUpdate(ctx context.Context, user *model.User) error {
	return r.db.WithContext(ctx).Save(user).Error
}

type UserFilter struct {
	Query  string
	Status model.UserStatus
	RoleID uint64
}

func (r *GormUserRepository) List(ctx context.Context, filter UserFilter, page, pageSize int) ([]model.User, int64, error) {
	var (
		users []model.User
		total int64
	)
	query := r.db.WithContext(ctx).Model(&model.User{})
	if filter.Query != "" {
		like := "%" + filter.Query + "%"
		query = query.Where("name LIKE ? OR email LIKE ?", like, like)
	}
	if filter.Status != "" {
		query = query.Where("status = ?", filter.Status)
	}
	if filter.RoleID > 0 {
		query = query.Joins("JOIN user_roles ur ON ur.user_id = users.id").Where("ur.role_id = ?", filter.RoleID).Distinct("users.id")
	}
	if err := query.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	if err := query.Preload("Roles").Preload("DefaultRole").Preload("Employee").Limit(pageSize).Offset((page - 1) * pageSize).Find(&users).Error; err != nil {
		return nil, 0, err
	}
	return users, total, nil
}

func (r *GormUserRepository) AssignRoles(ctx context.Context, userID uint64, roleIDs []uint64) error {
	var user model.User
	if err := r.db.WithContext(ctx).First(&user, userID).Error; err != nil {
		return err
	}
	var roles []model.Role
	if len(roleIDs) > 0 {
		if err := r.db.WithContext(ctx).Find(&roles, roleIDs).Error; err != nil {
			return err
		}
	}
	if err := r.db.WithContext(ctx).Model(&user).Association("Roles").Replace(roles); err != nil {
		return err
	}

	// If the user's default role is not in the new set, update it to the first provided role (if any).
	var defaultRoleID *uint64
	if len(roleIDs) > 0 {
		defaultRoleID = &roleIDs[0]
		if user.DefaultRoleID != nil && containsID(roleIDs, *user.DefaultRoleID) {
			defaultRoleID = user.DefaultRoleID
		}
	}
	return r.UpdateDefaultRole(ctx, userID, defaultRoleID)
}

func (r *GormUserRepository) RemoveRole(ctx context.Context, userID uint64, roleID uint64) error {
	var user model.User
	if err := r.db.WithContext(ctx).First(&user, userID).Error; err != nil {
		return err
	}
	role := model.Role{Base: model.Base{ID: roleID}}
	if err := r.db.WithContext(ctx).Model(&user).Association("Roles").Delete(&role); err != nil {
		return err
	}

	// Reconcile default role if it was removed
	if user.DefaultRoleID != nil && *user.DefaultRoleID == roleID {
		var remaining []model.Role
		if err := r.db.WithContext(ctx).Model(&user).Association("Roles").Find(&remaining); err != nil {
			return err
		}
		if len(remaining) == 0 {
			return r.UpdateDefaultRole(ctx, userID, nil)
		}
		nextID := remaining[0].ID
		return r.UpdateDefaultRole(ctx, userID, &nextID)
	}
	return nil
}

func (r *GormUserRepository) GetUserRoles(ctx context.Context, userID uint64) ([]model.Role, error) {
	var user model.User
	if err := r.db.WithContext(ctx).Preload("Roles.Permissions").First(&user, userID).Error; err != nil {
		return nil, err
	}
	return user.Roles, nil
}

func (r *GormUserRepository) GetUserPermissions(ctx context.Context, userID uint64) ([]model.Permission, error) {
	roles, err := r.GetUserRoles(ctx, userID)
	if err != nil {
		return nil, err
	}
	permMap := make(map[uint64]model.Permission)
	for _, role := range roles {
		for _, perm := range role.Permissions {
			permMap[perm.ID] = perm
		}
	}
	perms := make([]model.Permission, 0, len(permMap))
	for _, perm := range permMap {
		perms = append(perms, perm)
	}
	return perms, nil
}

func (r *GormUserRepository) UpdateDefaultRole(ctx context.Context, userID uint64, roleID *uint64) error {
	return r.db.WithContext(ctx).Model(&model.User{}).Where("id = ?", userID).Update("default_role_id", roleID).Error
}

func containsID(ids []uint64, target uint64) bool {
	for _, id := range ids {
		if id == target {
			return true
		}
	}
	return false
}
