package user

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

	"tm/pkg/audit"
	"tm/pkg/password"
	"tm/pkg/ratelimit"

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

var (
	errInvalidUserID            = errors.New("invalid id")
	errAdminUserNotFound        = errors.New("admin not found")
	errCannotResetPassword      = errors.New("cannot reset password for this user")
	errResetPasswordFailed      = errors.New("failed to reset password")
	errResetPasswordRateHit     = errors.New("too many password reset requests")
	errResetPasswordForbidden   = errors.New("you are not allowed to reset this password")
)

const adminPasswordResetRateLimit = 10

// AdminResetPassword generates a new password for the target admin user.
func (s *userService) AdminResetPassword(ctx context.Context, actorID, actorRole, targetID string) (*AdminResetPasswordResponse, error) {
	if UserRole(actorRole) != UserRoleAdmin {
		return nil, errResetPasswordForbidden
	}

	if _, err := bson.ObjectIDFromHex(targetID); err != nil {
		return nil, errInvalidUserID
	}

	if err := s.checkPasswordResetRateLimit(ctx, actorID, "admin"); err != nil {
		return nil, err
	}

	user, err := s.repository.GetByID(ctx, targetID)
	if err != nil {
		if errors.Is(err, ErrUserNotFound) {
			return nil, errAdminUserNotFound
		}
		s.logger.Error("Failed to load user for password reset", map[string]interface{}{
			"error":     err.Error(),
			"target_id": targetID,
		})
		return nil, errResetPasswordFailed
	}

	if user.Status != UserStatusActive {
		return nil, errCannotResetPassword
	}

	plainPassword, err := password.GenerateSecure()
	if err != nil {
		s.logger.Error("Failed to generate password", map[string]interface{}{
			"error":     err.Error(),
			"target_id": targetID,
		})
		return nil, errResetPasswordFailed
	}

	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(plainPassword), bcrypt.DefaultCost)
	if err != nil {
		s.logger.Error("Failed to hash generated password", map[string]interface{}{
			"error":     err.Error(),
			"target_id": targetID,
		})
		return nil, errResetPasswordFailed
	}

	if err := s.repository.UpdatePasswordReset(ctx, targetID, string(hashedPassword), actorID); err != nil {
		if errors.Is(err, ErrUserNotFound) {
			return nil, errAdminUserNotFound
		}
		s.logger.Error("Failed to persist reset password", map[string]interface{}{
			"error":     err.Error(),
			"target_id": targetID,
		})
		return nil, errResetPasswordFailed
	}

	if err := s.authService.InvalidateUserSessions(targetID); err != nil {
		s.logger.Error("Failed to invalidate user sessions after password reset", map[string]interface{}{
			"error":     err.Error(),
			"target_id": targetID,
		})
	}

	s.auditLogger.Log(ctx, audit.Entry{
		ActorID:    actorID,
		TargetType: audit.TargetTypeAdmin,
		TargetID:   targetID,
		Action:     audit.ActionPasswordReset,
	})

	s.logger.Info("Admin password reset completed", map[string]interface{}{
		"actor_id":  actorID,
		"target_id": targetID,
	})

	return &AdminResetPasswordResponse{Password: plainPassword}, nil
}

func (s *userService) checkPasswordResetRateLimit(ctx context.Context, actorID, resource string) error {
	if s.redisClient == nil {
		return nil
	}

	key := fmt.Sprintf("ratelimit:password_reset:%s:%s", actorID, resource)
	allowed, err := ratelimit.Allow(ctx, s.redisClient, key, adminPasswordResetRateLimit, time.Minute)
	if err != nil {
		s.logger.Warn("Password reset rate limit check failed", map[string]interface{}{
			"actor_id": actorID,
			"error":    err.Error(),
		})
		return nil
	}
	if !allowed {
		return errResetPasswordRateHit
	}
	return nil
}
