"""
Tests for UserDeletionService and AccountService.

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

from unittest.mock import patch

import pytest
from django.test import RequestFactory, TestCase

from apps.accounts.models import User
from apps.accounts.services import AccountService, UserDeletionService
from tests.factories import (
    ParkFactory,
    ParkReviewFactory,
    RideFactory,
    RideReviewFactory,
    SuperUserFactory,
    UserFactory,
)


@pytest.mark.django_db
class TestUserDeletionServiceGetOrCreateDeletedUser(TestCase):
    """Tests for UserDeletionService.get_or_create_deleted_user."""

    def test__get_or_create_deleted_user__first_call__creates_user(self):
        """Test get_or_create_deleted_user creates deleted user placeholder."""
        deleted_user = UserDeletionService.get_or_create_deleted_user()

        assert deleted_user.username == UserDeletionService.DELETED_USER_USERNAME
        assert deleted_user.email == UserDeletionService.DELETED_USER_EMAIL
        assert deleted_user.is_active is False
        assert deleted_user.is_banned is True

    def test__get_or_create_deleted_user__second_call__returns_existing(self):
        """Test get_or_create_deleted_user returns existing user on subsequent calls."""
        first_call = UserDeletionService.get_or_create_deleted_user()
        second_call = UserDeletionService.get_or_create_deleted_user()

        assert first_call.pk == second_call.pk


@pytest.mark.django_db
class TestUserDeletionServiceCanDeleteUser(TestCase):
    """Tests for UserDeletionService.can_delete_user."""

    def test__can_delete_user__regular_user__returns_true(self):
        """Test can_delete_user returns True for regular user."""
        user = UserFactory()

        can_delete, reason = UserDeletionService.can_delete_user(user)

        assert can_delete is True
        assert reason is None

    def test__can_delete_user__superuser__returns_false(self):
        """Test can_delete_user returns False for superuser."""
        user = SuperUserFactory()

        can_delete, reason = UserDeletionService.can_delete_user(user)

        assert can_delete is False
        assert "superuser" in reason.lower()

    def test__can_delete_user__deleted_user_placeholder__returns_false(self):
        """Test can_delete_user returns False for deleted user placeholder."""
        deleted_user = UserDeletionService.get_or_create_deleted_user()

        can_delete, reason = UserDeletionService.can_delete_user(deleted_user)

        assert can_delete is False
        assert "placeholder" in reason.lower()


@pytest.mark.django_db
class TestUserDeletionServiceDeleteUserPreserveSubmissions(TestCase):
    """Tests for UserDeletionService.delete_user_preserve_submissions."""

    def test__delete_user_preserve_submissions__user_with_reviews__preserves_reviews(self):
        """Test delete_user_preserve_submissions preserves user's reviews."""
        user = UserFactory()
        park = ParkFactory()
        ride = RideFactory()

        # Create reviews
        park_review = ParkReviewFactory(user=user, park=park)
        ride_review = RideReviewFactory(user=user, ride=ride)

        user_pk = user.pk

        UserDeletionService.delete_user_preserve_submissions(user)

        # User should be deleted
        assert not User.objects.filter(pk=user_pk).exists()

        # Reviews should still exist
        park_review.refresh_from_db()
        ride_review.refresh_from_db()

        deleted_user = UserDeletionService.get_or_create_deleted_user()
        assert park_review.user == deleted_user
        assert ride_review.user == deleted_user

    def test__delete_user_preserve_submissions__returns_summary(self):
        """Test delete_user_preserve_submissions returns correct summary."""
        user = UserFactory()
        park = ParkFactory()
        ParkReviewFactory(user=user, park=park)

        result = UserDeletionService.delete_user_preserve_submissions(user)

        assert "deleted_user" in result
        assert "preserved_submissions" in result
        assert "transferred_to" in result
        assert result["preserved_submissions"]["park_reviews"] == 1

    def test__delete_user_preserve_submissions__deleted_user_placeholder__raises_error(self):
        """Test delete_user_preserve_submissions raises error for placeholder."""
        deleted_user = UserDeletionService.get_or_create_deleted_user()

        with pytest.raises(ValueError):
            UserDeletionService.delete_user_preserve_submissions(deleted_user)


@pytest.mark.django_db
class TestAccountServiceValidatePassword(TestCase):
    """Tests for AccountService.validate_password."""

    def test__validate_password__valid_password__returns_true(self):
        """Test validate_password returns True for valid password."""
        result = AccountService.validate_password("SecurePass123")

        assert result is True

    def test__validate_password__too_short__returns_false(self):
        """Test validate_password returns False for short password."""
        result = AccountService.validate_password("Short1")

        assert result is False

    def test__validate_password__no_uppercase__returns_false(self):
        """Test validate_password returns False for password without uppercase."""
        result = AccountService.validate_password("lowercase123")

        assert result is False

    def test__validate_password__no_lowercase__returns_false(self):
        """Test validate_password returns False for password without lowercase."""
        result = AccountService.validate_password("UPPERCASE123")

        assert result is False

    def test__validate_password__no_numbers__returns_false(self):
        """Test validate_password returns False for password without numbers."""
        result = AccountService.validate_password("NoNumbers")

        assert result is False


@pytest.mark.django_db
class TestAccountServiceChangePassword(TestCase):
    """Tests for AccountService.change_password."""

    def test__change_password__correct_old_password__changes_password(self):
        """Test change_password changes password with correct old password."""
        user = UserFactory()
        user.set_password("OldPassword123")
        user.save()

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

        with patch.object(AccountService, "_send_password_change_confirmation"):
            result = AccountService.change_password(
                user=user,
                old_password="OldPassword123",
                new_password="NewPassword456",
                request=request,
            )

        assert result["success"] is True
        user.refresh_from_db()
        assert user.check_password("NewPassword456")

    def test__change_password__incorrect_old_password__returns_error(self):
        """Test change_password returns error with incorrect old password."""
        user = UserFactory()
        user.set_password("CorrectPassword123")
        user.save()

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

        result = AccountService.change_password(
            user=user,
            old_password="WrongPassword123",
            new_password="NewPassword456",
            request=request,
        )

        assert result["success"] is False
        assert "incorrect" in result["message"].lower()

    def test__change_password__weak_new_password__returns_error(self):
        """Test change_password returns error with weak new password."""
        user = UserFactory()
        user.set_password("OldPassword123")
        user.save()

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

        result = AccountService.change_password(
            user=user,
            old_password="OldPassword123",
            new_password="weak",  # Too weak
            request=request,
        )

        assert result["success"] is False
        assert "8 characters" in result["message"]


@pytest.mark.django_db
class TestAccountServiceInitiateEmailChange(TestCase):
    """Tests for AccountService.initiate_email_change."""

    @patch("apps.accounts.services.AccountService._send_email_verification")
    def test__initiate_email_change__valid_email__initiates_change(self, mock_send):
        """Test initiate_email_change initiates email change for valid email."""
        user = UserFactory()

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

        result = AccountService.initiate_email_change(
            user=user,
            new_email="newemail@example.com",
            request=request,
        )

        assert result["success"] is True
        user.refresh_from_db()
        assert user.pending_email == "newemail@example.com"
        mock_send.assert_called_once()

    def test__initiate_email_change__empty_email__returns_error(self):
        """Test initiate_email_change returns error for empty email."""
        user = UserFactory()

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

        result = AccountService.initiate_email_change(
            user=user,
            new_email="",
            request=request,
        )

        assert result["success"] is False
        assert "required" in result["message"].lower()

    def test__initiate_email_change__duplicate_email__returns_error(self):
        """Test initiate_email_change returns error for duplicate email."""
        UserFactory(email="existing@example.com")
        user = UserFactory()

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

        result = AccountService.initiate_email_change(
            user=user,
            new_email="existing@example.com",
            request=request,
        )

        assert result["success"] is False
        assert "already in use" in result["message"].lower()


@pytest.mark.django_db
class TestUserDeletionServiceRequestUserDeletion(TestCase):
    """Tests for UserDeletionService.request_user_deletion."""

    @patch("apps.accounts.services.UserDeletionService.send_deletion_verification_email")
    def test__request_user_deletion__regular_user__creates_request(self, mock_send):
        """Test request_user_deletion creates deletion request for regular user."""
        user = UserFactory()

        deletion_request = UserDeletionService.request_user_deletion(user)

        assert deletion_request.user == user
        assert deletion_request.verification_code is not None
        mock_send.assert_called_once()

    def test__request_user_deletion__superuser__raises_error(self):
        """Test request_user_deletion raises error for superuser."""
        user = SuperUserFactory()

        with pytest.raises(ValueError):
            UserDeletionService.request_user_deletion(user)


@pytest.mark.django_db
class TestUserDeletionServiceCancelDeletionRequest(TestCase):
    """Tests for UserDeletionService.cancel_deletion_request."""

    @patch("apps.accounts.services.UserDeletionService.send_deletion_verification_email")
    def test__cancel_deletion_request__existing_request__cancels_it(self, mock_send):
        """Test cancel_deletion_request cancels existing request."""
        user = UserFactory()
        UserDeletionService.request_user_deletion(user)

        result = UserDeletionService.cancel_deletion_request(user)

        assert result is True

    def test__cancel_deletion_request__no_request__returns_false(self):
        """Test cancel_deletion_request returns False when no request exists."""
        user = UserFactory()

        result = UserDeletionService.cancel_deletion_request(user)

        assert result is False
