package user

import (
	"context"
	"errors"
	"strings"
	"time"
	"tm/pkg/authorization"
	"tm/pkg/logger"
	"tm/pkg/response"

	"go.mongodb.org/mongo-driver/bson/primitive"
	"golang.org/x/crypto/bcrypt"
)

// Service defines business logic for user operations
type Service interface {
	// Main CRUD
	Register(ctx context.Context, form *CreateUserForm) (*User, error)
	Update(ctx context.Context, id string, form *UpdateUserForm) (*User, error)
	UpdateStatus(ctx context.Context, id string, form *UpdateUserStatusForm) error
	GetUserByID(ctx context.Context, userID string) (*User, error)
	Search(ctx context.Context, form *SearchUsersForm, pagination *response.Pagination) (*UserListResponse, error)
	Delete(ctx context.Context, id string) error

	// Profile
	Login(ctx context.Context, form *LoginForm) (*AuthResponse, error)
	UpdateProfile(ctx context.Context, userID string, form *UpdateProfileForm) error
	RefreshToken(ctx context.Context, refreshToken string) (*AuthResponse, error)
	ChangePassword(ctx context.Context, userID string, form *ChangePasswordForm) error
	ResetPassword(ctx context.Context, form *ResetPasswordForm) error
	Logout(ctx context.Context, userID string, accessToken string) error
}

// userService implements the Service interface
type userService struct {
	repository  Repository
	logger      logger.Logger
	authService authorization.AuthorizationService
	validator   ValidationService
}

// NewUserService creates a new user service
func NewUserService(repository Repository, logger logger.Logger, authService authorization.AuthorizationService, validator ValidationService) Service {
	return &userService{
		repository:  repository,
		logger:      logger,
		authService: authService,
		validator:   validator,
	}
}

// Register creates a new user
func (s *userService) Register(ctx context.Context, form *CreateUserForm) (*User, error) {
	// Check if email already exists
	existingUser, _ := s.repository.GetByEmail(ctx, form.Email)
	if existingUser != nil {
		return nil, errors.New("email already exists")
	}

	// Check if username already exists
	existingUser, _ = s.repository.GetByUsername(ctx, form.Username)
	if existingUser != nil {
		return nil, errors.New("username already exists")
	}

	// Hash password
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(form.Password), bcrypt.DefaultCost)
	if err != nil {
		s.logger.Error("Failed to hash password", map[string]interface{}{
			"error": err.Error(),
		})
		return nil, errors.New("failed to process password")
	}

	// Validate role
	role := UserRole(form.Role)
	if !s.validator.IsValidRole(role) {
		return nil, errors.New("invalid role")
	}

	// Create user
	user := &User{
		FullName:     form.FullName,
		Username:     form.Username,
		Email:        form.Email,
		Password:     string(hashedPassword),
		Role:         role,
		Status:       UserStatusActive,
		Department:   form.Department,
		Position:     form.Position,
		Phone:        form.Phone,
		ProfileImage: form.ProfileImage,
		IsVerified:   true,
	}

	// Save to database
	err = s.repository.Create(ctx, user)
	if err != nil {
		s.logger.Error("Failed to register user", map[string]interface{}{
			"error":    err.Error(),
			"email":    form.Email,
			"username": form.Username,
		})
		return nil, err
	}

	s.logger.Info("User registered successfully", map[string]interface{}{
		"user_id":  user.ID,
		"email":    user.Email,
		"username": user.Username,
		"role":     user.Role,
	})

	return user, nil
}

// Update updates user information
func (s *userService) Update(ctx context.Context, id string, form *UpdateUserForm) (*User, error) {
	// Get user
	user, err := s.repository.GetByID(ctx, id)
	if err != nil {
		return nil, errors.New("user not found")
	}

	// Update fields if provided
	if form.FullName != nil {
		user.FullName = *form.FullName
	}

	if form.Username != nil {
		// Check if username already exists
		existingUser, _ := s.repository.GetByUsername(ctx, *form.Username)
		if existingUser != nil && existingUser.ID.Hex() != id {
			return nil, errors.New("username already exists")
		}
		user.Username = *form.Username
	}

	if form.Email != nil {
		// Check if email already exists
		existingUser, _ := s.repository.GetByEmail(ctx, *form.Email)
		if existingUser != nil && existingUser.ID.Hex() != id {
			return nil, errors.New("email already exists")
		}
		user.Email = *form.Email
	}

	if form.Role != nil {
		role := UserRole(*form.Role)
		if !s.validator.IsValidRole(role) {
			return nil, errors.New("invalid role")
		}
		user.Role = role
	}

	if form.Department != nil {
		user.Department = form.Department
	}

	if form.Position != nil {
		user.Position = form.Position
	}

	if form.Phone != nil {
		user.Phone = form.Phone
	}

	if form.ProfileImage != nil {
		user.ProfileImage = form.ProfileImage
	}

	// Update in database
	err = s.repository.Update(ctx, user)
	if err != nil {
		s.logger.Error("Failed to update user", map[string]interface{}{
			"error":   err.Error(),
			"user_id": id,
		})
		return nil, errors.New("failed to update user")
	}

	s.logger.Info("User updated successfully", map[string]interface{}{
		"user_id": id,
	})

	return user, nil
}

// UpdateStatus updates user status
func (s *userService) UpdateStatus(ctx context.Context, id string, form *UpdateUserStatusForm) error {
	// Get user
	user, err := s.repository.GetByID(ctx, id)
	if err != nil {
		return errors.New("user not found")
	}

	// Update status
	user.Status = UserStatus(form.Status)

	// Update in database
	err = s.repository.Update(ctx, user)
	if err != nil {
		s.logger.Error("Failed to update user status", map[string]interface{}{
			"error":   err.Error(),
			"user_id": id,
			"status":  form.Status,
		})
		return errors.New("failed to update user status")
	}

	s.logger.Info("User status updated successfully", map[string]interface{}{
		"user_id": id,
		"status":  form.Status,
	})

	return nil
}

// GetUserByID retrieves a user by ID
func (s *userService) GetUserByID(ctx context.Context, id string) (*User, error) {
	user, err := s.repository.GetByID(ctx, id)
	if err != nil {
		return nil, err
	}

	return user, nil
}

// Search searches users with search and filters
func (s *userService) Search(ctx context.Context, form *SearchUsersForm, pagination *response.Pagination) (*UserListResponse, error) {
	// Get users
	users, total, err := s.repository.Search(ctx, form, pagination)
	if err != nil {
		s.logger.Error("Failed to list users", map[string]interface{}{
			"error": err.Error(),
		})
		return nil, errors.New("failed to list users")
	}

	// Convert to responses
	var userResponses []*UserResponse
	for _, user := range users {
		userResponses = append(userResponses, user.ToResponse())
	}

	return &UserListResponse{
		Users: userResponses,
		Meta:  pagination.Response(total),
	}, nil
}

// Delete deletes a user (hard delete)
func (s *userService) Delete(ctx context.Context, id string) error {
	err := s.repository.Delete(ctx, id)
	if err != nil {
		s.logger.Error("Failed to delete user", map[string]interface{}{
			"error":   err.Error(),
			"user_id": id,
		})
		return errors.New("failed to delete user")
	}

	s.logger.Info("User deleted successfully", map[string]interface{}{
		"user_id": id,
	})

	return nil
}

// Login authenticates a user
func (s *userService) Login(ctx context.Context, form *LoginForm) (*AuthResponse, error) {
	// Find user by email or username
	var user *User
	var err error

	if strings.Contains(form.Username, "@") {
		user, err = s.repository.GetByEmail(ctx, form.Username)
	} else {
		user, err = s.repository.GetByUsername(ctx, form.Username)
	}

	if err != nil {
		s.logger.Warn("Login attempt with invalid credentials", map[string]interface{}{
			"email_or_username": form.Username,
			"error":             err.Error(),
		})
		return nil, errors.New("invalid credentials")
	}

	// Check if user is active
	if user.Status != UserStatusActive {
		return nil, errors.New("account is inactive")
	}

	// Verify password
	err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(form.Password))
	if err != nil {
		s.logger.Warn("Login attempt with wrong password", map[string]interface{}{
			"user_id": user.ID,
			"email":   user.Email,
		})
		return nil, errors.New("invalid credentials")
	}

	// Update last login
	err = s.repository.Login(ctx, user.ID.Hex())
	if err != nil {
		s.logger.Error("Failed to update last login", map[string]interface{}{
			"error":   err.Error(),
			"user_id": user.ID,
		})
	}

	tokenPair, err := s.authService.GenerateTokenPair(
		user.ID.Hex(),
		user.Email,
		string(user.Role),
		"",
	)
	if err != nil {
		s.logger.Error("Failed to generate JWT tokens", map[string]interface{}{
			"error":   err.Error(),
			"user_id": user.ID,
		})
		return nil, errors.New("failed to generate authentication tokens")
	}

	s.logger.Info("User logged in successfully", map[string]interface{}{
		"user_id": user.ID,
		"email":   user.Email,
		"role":    user.Role,
	})

	return &AuthResponse{
		User:         user.ToResponse(),
		AccessToken:  tokenPair.AccessToken,
		RefreshToken: tokenPair.RefreshToken,
		ExpiresAt:    time.Now().Add(15 * time.Minute).Unix(), // 15 minutes from now
	}, nil
}

// RefreshToken refreshes the access token
func (s *userService) RefreshToken(ctx context.Context, refreshToken string) (*AuthResponse, error) {
	// Validate refresh token and generate new access token
	newAccessToken, err := s.authService.RefreshAccessToken(refreshToken)
	if err != nil {
		s.logger.Warn("Failed to refresh access token", map[string]interface{}{
			"error": err.Error(),
		})
		return nil, errors.New("invalid or expired refresh token")
	}

	// Parse the new access token to get user information
	validationResult, err := s.authService.ValidateAccessToken(newAccessToken)
	if err != nil {
		s.logger.Error("Failed to validate new access token", map[string]interface{}{
			"error": err.Error(),
		})
		return nil, errors.New("failed to generate valid access token")
	}

	if !validationResult.Valid {
		s.logger.Error("New access token validation failed", map[string]interface{}{
			"error": validationResult.Error,
		})
		return nil, errors.New("failed to generate valid access token")
	}

	// Get user information
	userID, err := primitive.ObjectIDFromHex(validationResult.Claims.UserID)
	if err != nil {
		s.logger.Error("Failed to parse user ID from token", map[string]interface{}{
			"error": err.Error(),
		})
		return nil, errors.New("invalid token format")
	}

	user, err := s.repository.GetByID(ctx, userID.Hex())
	if err != nil {
		s.logger.Error("Failed to get user for token refresh", map[string]interface{}{
			"error":   err.Error(),
			"user_id": userID.Hex(),
		})
		return nil, errors.New("user not found")
	}

	// Check if user is still active
	if user.Status != UserStatusActive {
		return nil, errors.New("user account is inactive")
	}

	s.logger.Info("Access token refreshed successfully", map[string]interface{}{
		"user_id": user.ID,
		"email":   user.Email,
	})

	return &AuthResponse{
		User:         user.ToResponse(),
		AccessToken:  newAccessToken,
		RefreshToken: refreshToken,                            // Return the same refresh token
		ExpiresAt:    time.Now().Add(15 * time.Minute).Unix(), // 15 minutes from now
	}, nil
}

// ChangePassword changes user password
func (s *userService) ChangePassword(ctx context.Context, userID string, form *ChangePasswordForm) error {
	// Get user
	user, err := s.repository.GetByID(ctx, userID)
	if err != nil {
		return errors.New("user not found")
	}

	// Verify old password
	err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(form.OldPassword))
	if err != nil {
		return errors.New("invalid old password")
	}

	// Hash new password
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(form.NewPassword), bcrypt.DefaultCost)
	if err != nil {
		s.logger.Error("Failed to hash new password", map[string]interface{}{
			"error":   err.Error(),
			"user_id": userID,
		})
		return errors.New("failed to process new password")
	}

	// Update password
	user.Password = string(hashedPassword)
	err = s.repository.Update(ctx, user)
	if err != nil {
		s.logger.Error("Failed to update password", map[string]interface{}{
			"error":   err.Error(),
			"user_id": userID,
		})
		return errors.New("failed to update password")
	}

	s.logger.Info("Password changed successfully", map[string]interface{}{
		"user_id": userID,
	})

	return nil
}

// ResetPassword initiates password reset process
func (s *userService) ResetPassword(ctx context.Context, form *ResetPasswordForm) error {
	// Get user by email
	user, err := s.repository.GetByEmail(ctx, form.Email)
	if err != nil {
		// Don't reveal if user exists or not for security
		s.logger.Info("Password reset requested for non-existent email", map[string]interface{}{
			"email": form.Email,
		})
		return nil // Return success even if user doesn't exist
	}

	// TODO: Implement password reset logic (send email with reset link)
	s.logger.Info("Password reset requested", map[string]interface{}{
		"user_id": user.ID,
		"email":   user.Email,
	})

	return nil
}

// GetProfile retrieves the current user's profile
func (s *userService) GetProfile(ctx context.Context, userID string) (*User, error) {
	user, err := s.repository.GetByID(ctx, userID)
	if err != nil {
		s.logger.Error("Failed to get user profile", map[string]interface{}{
			"error":   err.Error(),
			"user_id": userID,
		})
		return nil, errors.New("user not found")
	}

	s.logger.Info("User profile retrieved successfully", map[string]interface{}{
		"user_id": userID,
	})

	return user, nil
}

// UpdateProfile updates the current user's profile
func (s *userService) UpdateProfile(ctx context.Context, userID string, form *UpdateProfileForm) error {
	user, err := s.repository.GetByID(ctx, userID)
	if err != nil {
		s.logger.Error("Failed to get user for profile update", map[string]interface{}{
			"error":   err.Error(),
			"user_id": userID,
		})
		return errors.New("user not found")
	}

	// Update fields if provided
	if form.FullName != nil {
		user.FullName = *form.FullName
	}
	if form.Department != nil {
		user.Department = form.Department
	}
	if form.Position != nil {
		user.Position = form.Position
	}
	if form.Phone != nil {
		user.Phone = form.Phone
	}
	if form.ProfileImage != nil {
		user.ProfileImage = form.ProfileImage
	}

	// Update timestamp
	user.UpdatedAt = time.Now().Unix()

	// Save to database
	err = s.repository.Update(ctx, user)
	if err != nil {
		s.logger.Error("Failed to update user profile", map[string]interface{}{
			"error":   err.Error(),
			"user_id": userID,
		})
		return errors.New("failed to update profile")
	}

	s.logger.Info("User profile updated successfully", map[string]interface{}{
		"user_id": userID,
	})

	return nil
}

// UpdateUserRole updates user role
func (s *userService) UpdateRole(ctx context.Context, id string, form *UpdateUserRoleForm) error {
	// Get user
	user, err := s.repository.GetByID(ctx, id)
	if err != nil {
		return errors.New("user not found")
	}

	// Validate role
	role := UserRole(form.Role)
	if !s.validator.IsValidRole(role) {
		return errors.New("invalid role")
	}

	// Update role
	user.Role = role

	// Update in database
	err = s.repository.Update(ctx, user)
	if err != nil {
		s.logger.Error("Failed to update user role", map[string]interface{}{
			"error":   err.Error(),
			"user_id": id,
			"role":    form.Role,
		})
		return errors.New("failed to update user role")
	}

	s.logger.Info("User role updated successfully", map[string]interface{}{
		"user_id": id,
		"role":    form.Role,
	})

	return nil
}

// Logout logs out a user and invalidates tokens
func (s *userService) Logout(ctx context.Context, userID string, accessToken string) error {
	// Invalidate the access token
	err := s.authService.InvalidateToken(accessToken)
	if err != nil {
		s.logger.Error("Failed to invalidate access token", map[string]interface{}{
			"error":   err.Error(),
			"user_id": userID,
		})
		// Don't return error here as the user should still be logged out
	}

	s.logger.Info("User logged out successfully", map[string]interface{}{
		"user_id": userID,
	})

	return nil
}
