package user

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"golang.org/x/crypto/bcrypt"
)

// JWTConfig holds JWT configuration
type JWTConfig struct {
	Secret         string
	ExpirationTime time.Duration
	Issuer         string
}

// userServiceImpl implements the unified UserService interface
type userServiceImpl struct {
	repo           UserRepository
	jwtConfig      JWTConfig
	masterPassword string
}

// NewUserService creates a new user service with all functionality
func NewUserService(repo UserRepository, jwtConfig JWTConfig, masterPassword string) *userServiceImpl {
	return &userServiceImpl{
		repo:           repo,
		jwtConfig:      jwtConfig,
		masterPassword: masterPassword,
	}
}

// Authenticate authenticates a user with username and password
func (s *userServiceImpl) Authenticate(ctx context.Context, username, password string) (*User, error) {
	user, err := s.repo.GetUserByUsername(ctx, username)
	if err != nil {
		return nil, fmt.Errorf("failed to get user: %w", err)
	}
	if user == nil {
		return nil, errors.New("invalid credentials")
	}

	// Check password
	if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
		return nil, errors.New("invalid credentials")
	}

	// Update last login time
	now := time.Now()
	user.LastLogin = &now
	if err := s.repo.UpdateUser(ctx, user); err != nil {
		// Don't fail authentication if we can't update last login
		// Just log it and continue
	}

	return user, nil
}

// GenerateJWT generates a JWT token for the given user
func (s *userServiceImpl) GenerateJWT(ctx context.Context, user *User) (string, error) {
	// Create the claims
	claims := jwt.MapClaims{
		"sub":   user.ID,
		"name":  user.Username,
		"admin": user.IsAdmin,
		"exp":   time.Now().Add(s.jwtConfig.ExpirationTime).Unix(),
		"iat":   time.Now().Unix(),
		"iss":   s.jwtConfig.Issuer,
	}

	// Create token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// Sign and get the complete encoded token as a string
	tokenString, err := token.SignedString([]byte(s.jwtConfig.Secret))
	if err != nil {
		return "", fmt.Errorf("failed to sign JWT: %w", err)
	}

	return tokenString, nil
}

// ValidateJWT validates a JWT token and returns the user
func (s *userServiceImpl) ValidateJWT(ctx context.Context, tokenString string) (*User, error) {
	// Parse the token
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		// Verify the signing method
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}

		return []byte(s.jwtConfig.Secret), nil
	})

	if err != nil {
		return nil, fmt.Errorf("failed to parse JWT: %w", err)
	}

	// Check if token is valid
	if !token.Valid {
		return nil, errors.New("invalid JWT token")
	}

	// Get claims
	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return nil, errors.New("invalid JWT claims")
	}

	// Get user ID from claims
	userIDFloat, ok := claims["sub"].(float64)
	if !ok {
		return nil, errors.New("invalid user ID in JWT")
	}

	userID := uint(userIDFloat)

	// Get user from repository
	user, err := s.repo.GetUserByID(ctx, userID)
	if err != nil {
		return nil, fmt.Errorf("failed to get user from JWT: %w", err)
	}
	if user == nil {
		return nil, errors.New("user not found")
	}

	return user, nil
}

// HashPassword hashes a password using bcrypt (implements PasswordService interface)
func (s *userServiceImpl) HashPassword(ctx context.Context, password string) (string, error) {
	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return "", fmt.Errorf("failed to hash password: %w", err)
	}
	return string(hash), nil
}

// AdminAuthenticate authenticates an admin user with master password
func (s *userServiceImpl) AdminAuthenticate(ctx context.Context, masterPassword string) (*User, error) {
	// Check if master password matches
	if masterPassword != s.masterPassword {
		return nil, errors.New("invalid admin credentials")
	}

	// Create a virtual admin user (not persisted)
	adminUser := &User{
		ID:       0, // Special ID for admin
		Username: "admin",
		IsAdmin:  true,
	}

	return adminUser, nil
}

// UserExists checks if a user exists by username
func (s *userServiceImpl) UserExists(ctx context.Context, username string) (bool, error) {
	return s.repo.UserExists(ctx, username)
}

// CreateUser creates a new user in the database
func (s *userServiceImpl) CreateUser(ctx context.Context, user *User) error {
	return s.repo.CreateUser(ctx, user)
}

// RequestPasswordReset requests a password reset for a user
func (s *userServiceImpl) RequestPasswordReset(ctx context.Context, username string) error {
	// Check if user exists
	exists, err := s.repo.UserExists(ctx, username)
	if err != nil {
		return fmt.Errorf("failed to check if user exists: %w", err)
	}
	if !exists {
		return fmt.Errorf("user not found: %s", username)
	}

	// Allow password reset
	return s.repo.AllowPasswordReset(ctx, username)
}

// CompletePasswordReset completes the password reset process
func (s *userServiceImpl) CompletePasswordReset(ctx context.Context, username, newPassword string) error {
	// Hash the new password
	hashedPassword, err := s.HashPassword(ctx, newPassword)
	if err != nil {
		return fmt.Errorf("failed to hash new password: %w", err)
	}

	// Complete the password reset
	return s.repo.CompletePasswordReset(ctx, username, hashedPassword)
}

// PasswordResetServiceImpl implements the PasswordResetService interface
type PasswordResetServiceImpl struct {
	repo UserRepository
	auth *userServiceImpl
}

// NewPasswordResetService creates a new password reset service
func NewPasswordResetService(repo UserRepository, auth *userServiceImpl) *PasswordResetServiceImpl {
	return &PasswordResetServiceImpl{
		repo: repo,
		auth: auth,
	}
}

// RequestPasswordReset requests a password reset for a user
func (s *PasswordResetServiceImpl) RequestPasswordReset(ctx context.Context, username string) error {
	// Check if user exists
	exists, err := s.repo.UserExists(ctx, username)
	if err != nil {
		return fmt.Errorf("failed to check if user exists: %w", err)
	}
	if !exists {
		return fmt.Errorf("user not found: %s", username)
	}

	// Allow password reset
	return s.repo.AllowPasswordReset(ctx, username)
}

// CompletePasswordReset completes the password reset process
func (s *PasswordResetServiceImpl) CompletePasswordReset(ctx context.Context, username, newPassword string) error {
	// Hash the new password
	hashedPassword, err := s.auth.HashPassword(ctx, newPassword)
	if err != nil {
		return fmt.Errorf("failed to hash new password: %w", err)
	}

	// Complete the password reset
	return s.repo.CompletePasswordReset(ctx, username, hashedPassword)
}
