package services

import (
	"errors"
	"time"

	"gauth-central/internal/database"
	"gauth-central/internal/models"
	"gauth-central/pkg/utils"

	"github.com/markbates/goth"
	"gorm.io/gorm"
)

type AuthService struct {
	jwtService *JWTService
}

func NewAuthService() *AuthService {
	return &AuthService{
		jwtService: NewJWTService(),
	}
}

func (s *AuthService) Register(username, email, password string) (*models.User, string, string, string, error) {
	// Check if user exists (including soft-deleted users)
	var count int64
	database.DB.Model(&models.User{}).Unscoped().Where("email = ?", email).Count(&count)
	if count > 0 {
		return nil, "", "", "", errors.New("email already registered")
	}

	hashedPassword, err := utils.HashPassword(password)
	if err != nil {
		return nil, "", "", "", err
	}

	verifyToken, err := utils.GenerateSecureToken(32)
	if err != nil {
		return nil, "", "", "", err
	}

	// Email/password users must verify email; tokens are not issued until verified
	falseBool := false
	user := models.User{
		UserName:         username,
		Email:            email,
		Password:         hashedPassword,
		EmailVerified:    &falseBool, // Explicitly set to false
		EmailVerifyToken: verifyToken,
	}

	// Create user - EmailVerified will be false by default or explicitly set
	if err := database.DB.Create(&user).Error; err != nil {
		// Fallback check for duplicate key error just in case race condition
		if utils.IsDuplicateKeyError(err) {
			return nil, "", "", "", errors.New("email already registered")
		}
		return nil, "", "", "", err
	}

	// Assign default "user" role
	var userRole models.Role
	if err := database.DB.Where("name = ?", "user").First(&userRole).Error; err == nil {
		database.DB.Model(&user).Association("Roles").Append(&userRole)
	}

	// Reload user with roles (no JWT until email verified)
	database.DB.Preload("Roles.Permissions").First(&user, user.ID)

	return &user, "", "", verifyToken, nil
}

func (s *AuthService) Login(email, password string) (*models.User, string, string, error) {
	var user models.User
	// Preload Roles and Permissions
	if err := database.DB.Preload("Roles.Permissions").Where("email = ?", email).First(&user).Error; err != nil {
		return nil, "", "", errors.New("invalid credentials")
	}

	if !utils.CheckPasswordHash(password, user.Password) {
		return nil, "", "", errors.New("invalid credentials")
	}

	if !user.IsEmailVerified() {
		return nil, "", "", errors.New("email not verified")
	}

	accessToken, refreshToken, err := s.jwtService.GenerateTokenPair(user)
	if err != nil {
		return nil, "", "", err
	}

	return &user, accessToken, refreshToken, nil
}

func (s *AuthService) RefreshToken(refreshToken string) (string, string, error) {
	claims, err := s.jwtService.ValidateToken(refreshToken)
	if err != nil {
		return "", "", err
	}

	// Here you might want to check against DB if user still exists or is banned
	// Also you could implement token rotation (revoke used refresh token)

	var user models.User
	// Parse UUID from claims and preload permissions
	if err := database.DB.Preload("Roles.Permissions").Where("id = ?", claims.UserID).First(&user).Error; err != nil {
		return "", "", errors.New("user not found")
	}

	return s.jwtService.GenerateTokenPair(user)
}

func (s *AuthService) FindOrCreateSocialUser(gothUser goth.User, provider string) (*models.User, string, string, error) {
	var socialAccount models.SocialAccount
	var user models.User

	// Check if social account exists
	err := database.DB.Where("provider = ? AND provider_id = ?", provider, gothUser.UserID).First(&socialAccount).Error

	if err == nil {
		// Social account exists, get user
		if err := database.DB.Preload("Roles.Permissions").First(&user, socialAccount.UserID).Error; err != nil {
			return nil, "", "", err
		}

		// Update avatar if changed
		if gothUser.AvatarURL != "" && user.Avatar != gothUser.AvatarURL {
			database.DB.Model(&user).Update("avatar", gothUser.AvatarURL)
			user.Avatar = gothUser.AvatarURL
		}
	} else if errors.Is(err, gorm.ErrRecordNotFound) {
		// Check if user with same email exists
		err := database.DB.Where("email = ?", gothUser.Email).First(&user).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			// Create new user - use name from provider or generate from email
			username := gothUser.NickName
			if username == "" {
				username = gothUser.Name
			}
			if username == "" {
				// Generate username from email (part before @)
				for i, c := range gothUser.Email {
					if c == '@' {
						username = gothUser.Email[:i]
						break
					}
				}
			}

			// OAuth providers have already verified email; no email verification required
			trueBool := true
			user = models.User{
				UserName:      username,
				Email:         gothUser.Email,
				EmailVerified: &trueBool,
				Avatar:        gothUser.AvatarURL, // Save avatar from OAuth provider
			}
			if err := database.DB.Create(&user).Error; err != nil {
				return nil, "", "", err
			}
		} else {
			// Linking OAuth to existing user: treat as verified and update avatar
			updates := map[string]interface{}{
				"email_verified":     true,
				"email_verify_token": "",
			}
			if gothUser.AvatarURL != "" {
				updates["avatar"] = gothUser.AvatarURL
			}
			database.DB.Model(&user).Updates(updates)
			trueBool := true
			user.EmailVerified = &trueBool
			user.EmailVerifyToken = ""
			if gothUser.AvatarURL != "" {
				user.Avatar = gothUser.AvatarURL
			}
		}

		// Create social account with avatar and name
		socialAccount = models.SocialAccount{
			UserID:     user.ID,
			Provider:   provider,
			ProviderID: gothUser.UserID,
			Email:      gothUser.Email,
			Name:       gothUser.Name,
			AvatarURL:  gothUser.AvatarURL,
		}
		if err := database.DB.Create(&socialAccount).Error; err != nil {
			return nil, "", "", err
		}

		// Assign default "user" role
		var userRole models.Role
		if err := database.DB.Where("name = ?", "user").First(&userRole).Error; err == nil {
			database.DB.Model(&user).Association("Roles").Append(&userRole)
		}
	} else {
		return nil, "", "", err
	}

	// Make sure we have the user with all roles/permissions and social accounts loaded
	database.DB.Preload("Roles.Permissions").Preload("SocialAccounts").First(&user, user.ID)

	accessToken, refreshToken, err := s.jwtService.GenerateTokenPair(user)
	if err != nil {
		return nil, "", "", err
	}

	return &user, accessToken, refreshToken, nil
}

func (s *AuthService) GetUserByID(userID string) (*models.User, error) {
	var user models.User
	if err := database.DB.Preload("Roles.Permissions").Where("id = ?", userID).First(&user).Error; err != nil {
		return nil, errors.New("user not found")
	}
	return &user, nil
}

// VerifyEmail marks the user as verified when the token matches. Only for email/password registrations.
func (s *AuthService) VerifyEmail(token string) error {
	if token == "" {
		return errors.New("invalid verification token")
	}
	var user models.User
	if err := database.DB.Where("email_verify_token = ?", token).First(&user).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return errors.New("invalid or expired verification token")
		}
		return err
	}
	now := time.Now()
	return database.DB.Model(&user).Updates(map[string]interface{}{
		"email_verified":     true,
		"email_verify_token": "",
		"email_verified_at":  &now,
	}).Error
}
