package user

import (
	"context"
	"errors"
	"time"
	"tm/pkg/logger"
	orm "tm/pkg/mongo"
	"tm/pkg/response"

	"go.mongodb.org/mongo-driver/v2/bson"
	mongopkg "go.mongodb.org/mongo-driver/v2/mongo"
)

// Repository defines methods for user data access
type Repository interface {
	Login(ctx context.Context, id string) error
	Create(ctx context.Context, user *User) error
	Update(ctx context.Context, user *User) error
	Delete(ctx context.Context, id string) error
	GetByID(ctx context.Context, id string) (*User, error)
	UpdatePasswordReset(ctx context.Context, id, hashedPassword, updatedBy string) error
	GetByEmail(ctx context.Context, email string) (*User, error)
	GetByUsername(ctx context.Context, username string) (*User, error)
	GetByPhone(ctx context.Context, phone string) (*User, error)
	GetByIDs(ctx context.Context, userIDs []string) ([]User, error)
	Search(ctx context.Context, form *SearchUsersForm, pagination *response.Pagination) (*orm.PaginatedResult[User], error)
}

// userRepository implements the Repository interface using the MongoDB ORM
type userRepository struct {
	ormRepo     orm.Repository[User]
	collection  *mongopkg.Collection
	logger      logger.Logger
}

// NewUserRepository creates a new user repository
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
	// Create indexes using the ORM's index management
	indexes := []orm.Index{
		*orm.CreateTextIndex("search_idx", "full_name", "email", "username"),
		*orm.CreateUniqueIndex("phone_idx", bson.D{{Key: "phone", Value: 1}}).
			WithPartialFilterExpression(bson.M{"phone": bson.M{"$type": "string", "$ne": ""}}),
	}

	// Create indexes
	err := mongoManager.CreateIndexes("users", indexes)
	if err != nil {
		logger.Warn("Failed to create user indexes", map[string]interface{}{
			"error": err.Error(),
		})
	}

	// Create ORM repository
	collection := mongoManager.GetCollection("users")
	ormRepo := orm.NewRepository[User](collection, logger)

	return &userRepository{
		ormRepo:    ormRepo,
		collection: collection,
		logger:     logger,
	}
}

// Login updates the last login timestamp
func (r *userRepository) Login(ctx context.Context, id string) error {
	// Get user first
	user, err := r.GetByID(ctx, id)
	if err != nil {
		return err
	}

	// Update last login timestamp
	user.LastLoginAt = &[]int64{time.Now().Unix()}[0]
	user.SetUpdatedAt(time.Now().Unix())

	// Update in database
	err = r.ormRepo.Update(ctx, user)
	if err != nil {
		r.logger.Error("Failed to update last login", map[string]interface{}{
			"error":   err.Error(),
			"user_id": id,
		})
		return err
	}

	r.logger.Info("Last login updated successfully", map[string]interface{}{
		"user_id": id,
	})

	return nil
}

// Create creates a new user
func (r *userRepository) Create(ctx context.Context, user *User) error {
	// Set created timestamp using Unix timestamp
	now := time.Now().Unix()
	user.SetCreatedAt(now)

	// Use ORM to create user
	err := r.ormRepo.Create(ctx, user)
	if err != nil {
		r.logger.Error("Failed to create user", map[string]interface{}{
			"error":   err.Error(),
			"email":   user.Email,
			"user_id": user.ID,
		})
		return err
	}

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

	return nil
}

// Update updates a user
func (r *userRepository) Update(ctx context.Context, user *User) error {
	// Set updated timestamp using Unix timestamp
	user.SetUpdatedAt(time.Now().Unix())

	// Use ORM to update user
	err := r.ormRepo.Update(ctx, user)
	if err != nil {
		// Backward compatibility for legacy records with string _id.
		legacyFilterID := user.ID.Hex()
		updateDoc := bson.M{
			"$set": bson.M{
				"full_name":      user.FullName,
				"username":       user.Username,
				"email":          user.Email,
				"password":       user.Password,
				"role":           user.Role,
				"status":         user.Status,
				"department":     user.Department,
				"position":       user.Position,
				"phone":          user.Phone,
				"profile_image":  user.ProfileImage,
				"device_token":   user.DeviceToken,
				"is_verified":    user.IsVerified,
				"last_login_at":  user.LastLoginAt,
				"updated_at":     user.UpdatedAt,
				"updated_by":     user.UpdatedBy,
			},
		}
		legacyResult, legacyErr := r.collection.UpdateOne(ctx, bson.M{"_id": legacyFilterID}, updateDoc)
		if legacyErr == nil && legacyResult.MatchedCount > 0 {
			r.logger.Info("User updated successfully using legacy string ID", map[string]interface{}{
				"user_id": legacyFilterID,
				"email":   user.Email,
			})
			return nil
		}

		r.logger.Error("Failed to update user", map[string]interface{}{
			"error":   err.Error(),
			"user_id": user.ID,
		})
		return err
	}

	r.logger.Info("User updated successfully", map[string]interface{}{
		"user_id": user.ID,
		"email":   user.Email,
	})

	return nil
}

// GetByID retrieves a user by ID
func (r *userRepository) GetByID(ctx context.Context, id string) (*User, error) {
	user, err := r.ormRepo.FindByID(ctx, id)
	if err != nil {
		if errors.Is(err, orm.ErrDocumentNotFound) {
			// Backward compatibility: some legacy seed data may store _id as string instead of ObjectID.
			legacyUser, legacyErr := r.ormRepo.FindOne(ctx, bson.M{"_id": id})
			if legacyErr == nil {
				return legacyUser, nil
			}
			if errors.Is(legacyErr, orm.ErrDocumentNotFound) {
				return nil, ErrUserNotFound
			}
			r.logger.Error("Failed to get user by legacy string ID", map[string]interface{}{
				"error":   legacyErr.Error(),
				"user_id": id,
			})
			return nil, legacyErr
		}
		r.logger.Error("Failed to get user by ID", map[string]interface{}{
			"error":   err.Error(),
			"user_id": id,
		})
		return nil, err
	}

	return user, nil
}

// UpdatePasswordReset atomically persists a new password hash and audit fields.
func (r *userRepository) UpdatePasswordReset(ctx context.Context, id, hashedPassword, updatedBy string) error {
	now := time.Now().Unix()
	updateDoc := bson.M{
		"$set": bson.M{
			"password":   hashedPassword,
			"updated_at": now,
			"updated_by": updatedBy,
		},
	}

	objectID, err := bson.ObjectIDFromHex(id)
	if err == nil {
		result, updateErr := r.collection.UpdateOne(ctx, bson.M{"_id": objectID}, updateDoc)
		if updateErr == nil && result.MatchedCount > 0 {
			return nil
		}
		if updateErr != nil {
			return updateErr
		}
	}

	result, err := r.collection.UpdateOne(ctx, bson.M{"_id": id}, updateDoc)
	if err != nil {
		r.logger.Error("Failed to update password reset", map[string]interface{}{
			"error":   err.Error(),
			"user_id": id,
		})
		return err
	}
	if result.MatchedCount == 0 {
		return ErrUserNotFound
	}

	return nil
}

// GetByEmail retrieves a user by email
func (r *userRepository) GetByEmail(ctx context.Context, email string) (*User, error) {
	user, err := r.ormRepo.FindOne(ctx, bson.M{"email": email})
	if err != nil {
		if errors.Is(err, orm.ErrDocumentNotFound) {
			return nil, errors.New("user not found")
		}
		r.logger.Error("Failed to get user by email", map[string]interface{}{
			"error": err.Error(),
			"email": email,
		})
		return nil, err
	}

	return user, nil
}

// GetByPhone retrieves a user by phone number
func (r *userRepository) GetByPhone(ctx context.Context, phone string) (*User, error) {
	user, err := r.ormRepo.FindOne(ctx, bson.M{"phone": phone})
	if err != nil {
		if errors.Is(err, orm.ErrDocumentNotFound) {
			return nil, errors.New("user not found")
		}
		r.logger.Error("Failed to get user by phone", map[string]interface{}{
			"error": err.Error(),
			"phone": phone,
		})
		return nil, err
	}

	return user, nil
}

// GetByUsername retrieves a user by username
func (r *userRepository) GetByUsername(ctx context.Context, username string) (*User, error) {
	user, err := r.ormRepo.FindOne(ctx, bson.M{"username": username})
	if err != nil {
		if errors.Is(err, orm.ErrDocumentNotFound) {
			return nil, errors.New("user not found")
		}
		r.logger.Error("Failed to get user by username", map[string]interface{}{
			"error":    err.Error(),
			"username": username,
		})
		return nil, err
	}

	return user, nil
}

// Delete deletes a user (soft delete by setting status to inactive)
func (r *userRepository) Delete(ctx context.Context, id string) error {
	// Get user first
	_, err := r.GetByID(ctx, id)
	if err != nil {
		r.logger.Error("Failed to delete user", map[string]interface{}{
			"error":   err.Error(),
			"user_id": id,
		})
		if errors.Is(err, orm.ErrDocumentNotFound) {
			return errors.New("user not found")
		}

		return err
	}

	// Delete in database
	err = r.ormRepo.Delete(ctx, id)
	if err != nil {
		r.logger.Error("Failed to delete user", map[string]interface{}{
			"error":   err.Error(),
			"user_id": id,
		})
		return err
	}

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

	return nil
}

// GetByIDs retrieves users by IDs
func (r *userRepository) GetByIDs(ctx context.Context, userIDs []string) ([]User, error) {
	objectIDs := make([]bson.ObjectID, 0)
	for _, id := range userIDs {
		objectID, err := bson.ObjectIDFromHex(id)
		if err != nil {
			return nil, err
		}
		objectIDs = append(objectIDs, objectID)
	}

	users, err := r.ormRepo.FindAll(ctx, bson.M{"_id": bson.M{"$in": objectIDs}}, orm.NewPaginationBuilder().Limit(len(userIDs)).Build())
	if err != nil {
		return nil, err
	}

	return users.Items, nil
}

// Search retrieves users with pagination
func (r *userRepository) Search(ctx context.Context, form *SearchUsersForm, pagination *response.Pagination) (*orm.PaginatedResult[User], error) {
	filter := bson.M{}

	if form.Search != nil {
		filter["$or"] = bson.A{
			bson.M{"full_name": bson.M{"$regex": form.Search, "$options": "i"}},
			bson.M{"email": bson.M{"$regex": form.Search, "$options": "i"}},
			bson.M{"username": bson.M{"$regex": form.Search, "$options": "i"}},
		}
	}

	if form.Status != nil {
		filter["status"] = form.Status
	}

	if form.Role != nil {
		filter["role"] = form.Role
	}

	mongoPagination, err := orm.BuildListPagination(
		pagination.Limit,
		pagination.Offset,
		pagination.Cursor,
		pagination.SortBy,
		pagination.SortOrder,
		filter,
		orm.ListPaginationOptions{},
	)
	if err != nil {
		return nil, err
	}

	result, err := r.ormRepo.FindAll(ctx, filter, mongoPagination)
	if err != nil {
		r.logger.Error("Failed to list users", map[string]interface{}{
			"error": err.Error(),
		})
		return nil, err
	}

	return result, nil
}
