package repository

import (
	"time"

	"hyapp-admin-server/internal/model"

	"gorm.io/gorm"
)

// UserAuthorization is the current server-side authorization state used for
// every protected request. JWT claims identify the session, but never own RBAC
// state because role changes must take effect before a long-lived token expires.
type UserAuthorization struct {
	Username    string
	Status      string
	Permissions []string
}

type userAuthorizationRow struct {
	Username       string
	Status         string
	PermissionCode *string
}

func (s *Store) FindUserByUsername(username string) (*model.User, error) {
	var user model.User
	err := s.db.Preload("Roles.Permissions").Preload("TeamRecord").Where("username = ?", username).First(&user).Error
	if err != nil {
		return nil, err
	}

	return &user, nil
}

func (s *Store) FindUserByID(id uint) (*model.User, error) {
	var user model.User
	err := s.db.Preload("Roles.Permissions").Preload("TeamRecord").First(&user, id).Error
	if err != nil {
		return nil, err
	}

	return &user, nil
}

func (s *Store) CurrentAuthorizationForUser(userID uint) (UserAuthorization, error) {
	if userID == 0 {
		return UserAuthorization{}, gorm.ErrRecordNotFound
	}

	var rows []userAuthorizationRow
	// The query starts from admin_users.id and follows the two composite-primary-key
	// relation tables. It therefore resolves live permissions in one indexed query
	// without loading the full user/role models on every protected HTTP request.
	result := s.db.Raw(`
		SELECT admin_users.username,
		       admin_users.status,
		       admin_permissions.code AS permission_code
		FROM admin_users
		LEFT JOIN admin_user_roles
		       ON admin_user_roles.user_id = admin_users.id
		LEFT JOIN admin_role_permissions
		       ON admin_role_permissions.role_id = admin_user_roles.role_id
		LEFT JOIN admin_permissions
		       ON admin_permissions.id = admin_role_permissions.permission_id
		WHERE admin_users.id = ?
		ORDER BY admin_permissions.code ASC
	`, userID).Scan(&rows)
	if result.Error != nil {
		return UserAuthorization{}, result.Error
	}
	if len(rows) == 0 {
		return UserAuthorization{}, gorm.ErrRecordNotFound
	}

	authorization := UserAuthorization{
		Username: rows[0].Username,
		Status:   rows[0].Status,
	}
	seen := make(map[string]struct{}, len(rows))
	for _, row := range rows {
		if row.PermissionCode == nil || *row.PermissionCode == "" {
			continue
		}
		if _, ok := seen[*row.PermissionCode]; ok {
			continue
		}
		seen[*row.PermissionCode] = struct{}{}
		authorization.Permissions = append(authorization.Permissions, *row.PermissionCode)
	}
	return authorization, nil
}

func PermissionsForUser(user model.User) []string {
	set := map[string]struct{}{}
	for _, role := range user.Roles {
		for _, permission := range role.Permissions {
			set[permission.Code] = struct{}{}
		}
	}

	out := make([]string, 0, len(set))
	for code := range set {
		out = append(out, code)
	}

	return out
}

func (s *Store) TouchUserLogin(id uint) error {
	nowMS := time.Now().UTC().UnixMilli()
	return s.db.Model(&model.User{}).Where("id = ?", id).Updates(map[string]any{
		"last_login_at_ms": &nowMS,
		"updated_at_ms":    nowMS,
	}).Error
}

func (s *Store) CreateRefreshToken(token model.RefreshToken) error {
	return s.db.Create(&token).Error
}

func (s *Store) RotateRefreshToken(oldTokenID uint, token model.RefreshToken) error {
	nowMS := time.Now().UTC().UnixMilli()
	return s.db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Create(&token).Error; err != nil {
			return err
		}
		return tx.Model(&model.RefreshToken{}).
			Where("id = ? AND revoked_at_ms IS NULL", oldTokenID).
			Update("revoked_at_ms", &nowMS).Error
	})
}

func (s *Store) FindRefreshToken(hash string) (*model.RefreshToken, error) {
	var token model.RefreshToken
	err := s.db.Where("token_hash = ? AND revoked_at_ms IS NULL AND expires_at_ms > ?", hash, time.Now().UTC().UnixMilli()).First(&token).Error
	if err != nil {
		return nil, err
	}

	return &token, nil
}

func (s *Store) RevokeRefreshToken(hash string) error {
	nowMS := time.Now().UTC().UnixMilli()
	return s.db.Model(&model.RefreshToken{}).Where("token_hash = ? AND revoked_at_ms IS NULL", hash).Update("revoked_at_ms", &nowMS).Error
}
