"""
Tests for Account serializers.

Following Django styleguide pattern: test__<context>__<action>__<expected_outcome>
"""

from unittest.mock import Mock, patch

import pytest
from django.test import RequestFactory, TestCase

from apps.accounts.serializers import (
    LoginSerializer,
    PasswordChangeSerializer,
    PasswordResetSerializer,
    SignupSerializer,
    SocialProviderSerializer,
    UserSerializer,
)
from apps.api.v1.accounts.serializers import (
    UserProfileCreateInputSerializer,
    UserProfileOutputSerializer,
    UserProfileUpdateInputSerializer,
)
from tests.factories import (
    UserFactory,
)


@pytest.mark.django_db
class TestUserSerializer(TestCase):
    """Tests for UserSerializer."""

    def test__serialize__user__returns_expected_fields(self):
        """Test serializing a user returns expected fields."""
        user = UserFactory()

        serializer = UserSerializer(user)
        data = serializer.data

        assert "id" in data
        assert "username" in data
        assert "email" in data
        assert "display_name" in data
        assert "date_joined" in data
        assert "is_active" in data
        assert "avatar_url" in data

    def test__serialize__user_without_profile__returns_none_avatar(self):
        """Test serializing user without profile returns None for avatar."""
        user = UserFactory()
        # Ensure no profile
        if hasattr(user, "profile"):
            user.profile.delete()

        serializer = UserSerializer(user)
        data = serializer.data

        assert data["avatar_url"] is None

    def test__get_display_name__user_with_display_name__returns_display_name(self):
        """Test get_display_name returns user's display name."""
        user = UserFactory()
        user.display_name = "John Doe"
        user.save()

        serializer = UserSerializer(user)

        # get_display_name calls the model method
        assert "display_name" in serializer.data

    def test__meta__read_only_fields__includes_id_and_dates(self):
        """Test Meta.read_only_fields includes id and date fields."""
        assert "id" in UserSerializer.Meta.read_only_fields
        assert "date_joined" in UserSerializer.Meta.read_only_fields
        assert "is_active" in UserSerializer.Meta.read_only_fields


class TestLoginSerializer(TestCase):
    """Tests for LoginSerializer."""

    def test__validate__valid_credentials__returns_data(self):
        """Test validation passes with valid credentials."""
        data = {
            "username": "testuser",
            "password": "testpassword123",
        }

        serializer = LoginSerializer(data=data)

        assert serializer.is_valid(), serializer.errors
        assert serializer.validated_data["username"] == "testuser"
        assert serializer.validated_data["password"] == "testpassword123"

    def test__validate__email_as_username__returns_data(self):
        """Test validation passes with email as username."""
        data = {
            "username": "user@example.com",
            "password": "testpassword123",
        }

        serializer = LoginSerializer(data=data)

        assert serializer.is_valid(), serializer.errors
        assert serializer.validated_data["username"] == "user@example.com"

    def test__validate__missing_username__returns_error(self):
        """Test validation fails with missing username."""
        data = {"password": "testpassword123"}

        serializer = LoginSerializer(data=data)

        assert not serializer.is_valid()
        assert "username" in serializer.errors

    def test__validate__missing_password__returns_error(self):
        """Test validation fails with missing password."""
        data = {"username": "testuser"}

        serializer = LoginSerializer(data=data)

        assert not serializer.is_valid()
        assert "password" in serializer.errors

    def test__validate__empty_credentials__returns_error(self):
        """Test validation fails with empty credentials."""
        data = {"username": "", "password": ""}

        serializer = LoginSerializer(data=data)

        assert not serializer.is_valid()


@pytest.mark.django_db
class TestSignupSerializer(TestCase):
    """Tests for SignupSerializer."""

    def test__validate__valid_data__returns_validated_data(self):
        """Test validation passes with valid signup data."""
        data = {
            "username": "newuser",
            "email": "newuser@example.com",
            "display_name": "New User",
            "password": "SecurePass123!",
            "password_confirm": "SecurePass123!",
        }

        serializer = SignupSerializer(data=data)

        assert serializer.is_valid(), serializer.errors

    def test__validate__mismatched_passwords__returns_error(self):
        """Test validation fails with mismatched passwords."""
        data = {
            "username": "newuser",
            "email": "newuser@example.com",
            "display_name": "New User",
            "password": "SecurePass123!",
            "password_confirm": "DifferentPass456!",
        }

        serializer = SignupSerializer(data=data)

        assert not serializer.is_valid()
        assert "password_confirm" in serializer.errors

    def test__validate_email__duplicate_email__returns_error(self):
        """Test validation fails with duplicate email."""
        UserFactory(email="existing@example.com")
        data = {
            "username": "newuser",
            "email": "existing@example.com",
            "display_name": "New User",
            "password": "SecurePass123!",
            "password_confirm": "SecurePass123!",
        }

        serializer = SignupSerializer(data=data)

        assert not serializer.is_valid()
        assert "email" in serializer.errors

    def test__validate_email__case_insensitive__returns_error(self):
        """Test email validation is case insensitive."""
        UserFactory(email="existing@example.com")
        data = {
            "username": "newuser",
            "email": "EXISTING@EXAMPLE.COM",
            "display_name": "New User",
            "password": "SecurePass123!",
            "password_confirm": "SecurePass123!",
        }

        serializer = SignupSerializer(data=data)

        assert not serializer.is_valid()
        assert "email" in serializer.errors

    def test__validate_username__duplicate_username__returns_error(self):
        """Test validation fails with duplicate username."""
        UserFactory(username="existinguser")
        data = {
            "username": "existinguser",
            "email": "new@example.com",
            "display_name": "New User",
            "password": "SecurePass123!",
            "password_confirm": "SecurePass123!",
        }

        serializer = SignupSerializer(data=data)

        assert not serializer.is_valid()
        assert "username" in serializer.errors

    def test__validate__weak_password__returns_error(self):
        """Test validation fails with weak password."""
        data = {
            "username": "newuser",
            "email": "newuser@example.com",
            "display_name": "New User",
            "password": "123",  # Too weak
            "password_confirm": "123",
        }

        serializer = SignupSerializer(data=data)

        assert not serializer.is_valid()
        # Password validation error could be in 'password' or 'non_field_errors'
        assert "password" in serializer.errors or "non_field_errors" in serializer.errors

    def test__create__valid_data__creates_user(self):
        """Test create method creates user correctly."""
        data = {
            "username": "createuser",
            "email": "createuser@example.com",
            "display_name": "Create User",
            "password": "SecurePass123!",
            "password_confirm": "SecurePass123!",
        }

        serializer = SignupSerializer(data=data)
        assert serializer.is_valid(), serializer.errors

        user = serializer.save()

        assert user.username == "createuser"
        assert user.email == "createuser@example.com"
        assert user.check_password("SecurePass123!")

    def test__meta__password_write_only__excludes_from_output(self):
        """Test password field is write-only."""
        assert "password" in SignupSerializer.Meta.fields
        assert SignupSerializer.Meta.extra_kwargs.get("password", {}).get("write_only") is True


@pytest.mark.django_db
class TestPasswordResetSerializer(TestCase):
    """Tests for PasswordResetSerializer."""

    def test__validate__valid_email__returns_normalized_email(self):
        """Test validation normalizes email."""
        UserFactory(email="test@example.com")
        data = {"email": "  TEST@EXAMPLE.COM  "}

        serializer = PasswordResetSerializer(data=data)

        assert serializer.is_valid(), serializer.errors
        assert serializer.validated_data["email"] == "test@example.com"

    def test__validate__nonexistent_email__still_valid(self):
        """Test validation passes with nonexistent email (security)."""
        data = {"email": "nonexistent@example.com"}

        serializer = PasswordResetSerializer(data=data)

        # Should pass validation to prevent email enumeration
        assert serializer.is_valid(), serializer.errors

    def test__validate__existing_email__attaches_user(self):
        """Test validation attaches user when email exists."""
        user = UserFactory(email="exists@example.com")
        data = {"email": "exists@example.com"}

        serializer = PasswordResetSerializer(data=data)
        serializer.is_valid()

        assert hasattr(serializer, "user")
        assert serializer.user == user

    def test__validate__nonexistent_email__no_user_attached(self):
        """Test validation doesn't attach user for nonexistent email."""
        data = {"email": "notfound@example.com"}

        serializer = PasswordResetSerializer(data=data)
        serializer.is_valid()

        assert not hasattr(serializer, "user")

    @patch("apps.accounts.serializers.EmailService.send_email")
    def test__save__existing_user__sends_email(self, mock_send_email):
        """Test save sends email for existing user."""
        UserFactory(email="reset@example.com")
        data = {"email": "reset@example.com"}

        factory = RequestFactory()
        request = factory.post("/password-reset/")

        serializer = PasswordResetSerializer(data=data, context={"request": request})
        serializer.is_valid()
        serializer.save()

        # Email should be sent
        mock_send_email.assert_called_once()


@pytest.mark.django_db
class TestPasswordChangeSerializer(TestCase):
    """Tests for PasswordChangeSerializer."""

    def test__validate__valid_data__returns_validated_data(self):
        """Test validation passes with valid password change data."""
        user = UserFactory()
        user.set_password("OldPass123!")
        user.save()

        factory = RequestFactory()
        request = factory.post("/password-change/")
        request.user = user

        data = {
            "old_password": "OldPass123!",
            "new_password": "NewSecurePass456!",
            "new_password_confirm": "NewSecurePass456!",
        }

        serializer = PasswordChangeSerializer(data=data, context={"request": request})

        assert serializer.is_valid(), serializer.errors

    def test__validate_old_password__incorrect__returns_error(self):
        """Test validation fails with incorrect old password."""
        user = UserFactory()
        user.set_password("CorrectOldPass!")
        user.save()

        factory = RequestFactory()
        request = factory.post("/password-change/")
        request.user = user

        data = {
            "old_password": "WrongOldPass!",
            "new_password": "NewSecurePass456!",
            "new_password_confirm": "NewSecurePass456!",
        }

        serializer = PasswordChangeSerializer(data=data, context={"request": request})

        assert not serializer.is_valid()
        assert "old_password" in serializer.errors

    def test__validate__mismatched_new_passwords__returns_error(self):
        """Test validation fails with mismatched new passwords."""
        user = UserFactory()
        user.set_password("OldPass123!")
        user.save()

        factory = RequestFactory()
        request = factory.post("/password-change/")
        request.user = user

        data = {
            "old_password": "OldPass123!",
            "new_password": "NewSecurePass456!",
            "new_password_confirm": "DifferentPass789!",
        }

        serializer = PasswordChangeSerializer(data=data, context={"request": request})

        assert not serializer.is_valid()
        assert "new_password_confirm" in serializer.errors

    def test__save__valid_data__changes_password(self):
        """Test save changes the password."""
        user = UserFactory()
        user.set_password("OldPass123!")
        user.save()

        factory = RequestFactory()
        request = factory.post("/password-change/")
        request.user = user

        data = {
            "old_password": "OldPass123!",
            "new_password": "NewSecurePass456!",
            "new_password_confirm": "NewSecurePass456!",
        }

        serializer = PasswordChangeSerializer(data=data, context={"request": request})
        assert serializer.is_valid(), serializer.errors
        serializer.save()

        user.refresh_from_db()
        assert user.check_password("NewSecurePass456!")


class TestSocialProviderSerializer(TestCase):
    """Tests for SocialProviderSerializer."""

    def test__validate__valid_provider__returns_data(self):
        """Test validation passes with valid provider data."""
        data = {
            "id": "google",
            "name": "Google",
            "login_url": "https://accounts.google.com/oauth/login",
        }

        serializer = SocialProviderSerializer(data=data)

        assert serializer.is_valid(), serializer.errors
        assert serializer.validated_data["id"] == "google"
        assert serializer.validated_data["name"] == "Google"

    def test__validate__invalid_url__returns_error(self):
        """Test validation fails with invalid URL."""
        data = {
            "id": "invalid",
            "name": "Invalid Provider",
            "login_url": "not-a-valid-url",
        }

        serializer = SocialProviderSerializer(data=data)

        assert not serializer.is_valid()
        assert "login_url" in serializer.errors


@pytest.mark.django_db
class TestUserProfileOutputSerializer(TestCase):
    """Tests for UserProfileOutputSerializer."""

    def test__serialize__profile__returns_expected_fields(self):
        """Test serializing profile returns expected fields."""
        user = UserFactory()

        # Create mock profile
        mock_profile = Mock()
        mock_profile.user = user
        mock_profile.avatar = None

        serializer = UserProfileOutputSerializer(mock_profile)

        # Should include user nested serializer
        assert "user" in serializer.data or serializer.data is not None


@pytest.mark.django_db
class TestUserProfileCreateInputSerializer(TestCase):
    """Tests for UserProfileCreateInputSerializer."""

    def test__meta__fields__includes_all_fields(self):
        """Test Meta.fields is set to __all__."""
        assert UserProfileCreateInputSerializer.Meta.fields == "__all__"


@pytest.mark.django_db
class TestUserProfileUpdateInputSerializer(TestCase):
    """Tests for UserProfileUpdateInputSerializer."""

    def test__meta__user_read_only(self):
        """Test user field is read-only for updates."""
        extra_kwargs = UserProfileUpdateInputSerializer.Meta.extra_kwargs
        assert extra_kwargs.get("user", {}).get("read_only") is True
