from collections.abc import Sequence
from typing import Any
from unittest.mock import Mock

from factory import Faker
from factory import post_generation
from factory.django import DjangoModelFactory

from smoothschedule.identity.users.models import User


class UserFactory(DjangoModelFactory[User]):
    """
    Factory for creating User instances.

    This factory can be used with the database for integration tests,
    or you can use create_mock_user() for unit tests that don't need DB access.
    """
    username = Faker("user_name")
    email = Faker("email")
    name = Faker("name")

    @post_generation
    def password(self, create: bool, extracted: Sequence[Any], **kwargs):  # noqa: FBT001
        password = (
            extracted
            if extracted
            else Faker(
                "password",
                length=42,
                special_chars=True,
                digits=True,
                upper_case=True,
                lower_case=True,
            ).evaluate(None, None, extra={"locale": None})
        )
        self.set_password(password)

    @classmethod
    def _after_postgeneration(cls, instance, create, results=None):
        """Save again the instance if creating and at least one hook ran."""
        if create and results and not cls._meta.skip_postgeneration_save:
            # Some post-generation hooks ran, and may have modified us.
            instance.save()

    class Meta:
        model = User
        django_get_or_create = ["username"]


def create_mock_user(**kwargs):
    """
    Create a mocked User instance for unit tests that don't need database access.

    Usage:
        mock_user = create_mock_user(username="testuser", email="test@example.com")
        mock_user.username  # Returns "testuser"

    Args:
        **kwargs: User attributes to set on the mock

    Returns:
        Mock: A mocked User instance with the specified attributes
    """
    defaults = {
        'id': 1,
        'username': Faker("user_name").evaluate(None, None, extra={"locale": None}),
        'email': Faker("email").evaluate(None, None, extra={"locale": None}),
        'name': Faker("name").evaluate(None, None, extra={"locale": None}),
        'is_authenticated': True,
        'is_active': True,
        'is_staff': False,
        'is_superuser': False,
    }
    defaults.update(kwargs)

    mock_user = Mock(spec=User)
    for key, value in defaults.items():
        setattr(mock_user, key, value)

    return mock_user
