from datetime import datetime, timedelta

from django.test import TestCase

from authentik.core.tests.utils import create_test_user
from authentik.enterprise.policies.unique_password.models import (
    UniquePasswordPolicy,
    UserPasswordHistory,
)
from authentik.enterprise.policies.unique_password.tasks import (
    check_and_purge_password_history,
    trim_password_histories,
)
from authentik.policies.models import PolicyBinding, PolicyBindingModel


class TestUniquePasswordPolicyModel(TestCase):
    """Test the UniquePasswordPolicy model methods"""

    def test_is_in_use_with_binding(self):
        """Test is_in_use returns True when a policy binding exists"""
        # Create a UniquePasswordPolicy and a PolicyBinding for it
        policy = UniquePasswordPolicy.objects.create(num_historical_passwords=5)
        pbm = PolicyBindingModel.objects.create()
        PolicyBinding.objects.create(target=pbm, policy=policy, order=0, enabled=True)

        # Verify is_in_use returns True
        self.assertTrue(UniquePasswordPolicy.is_in_use())

    def test_is_in_use_with_promptstage(self):
        """Test is_in_use returns True when attached to a PromptStage"""
        from authentik.stages.prompt.models import PromptStage

        # Create a UniquePasswordPolicy and attach it to a PromptStage
        policy = UniquePasswordPolicy.objects.create(num_historical_passwords=5)
        prompt_stage = PromptStage.objects.create(
            name="Test Prompt Stage",
        )
        # Use the set() method for many-to-many relationships
        prompt_stage.validation_policies.set([policy])

        # Verify is_in_use returns True
        self.assertTrue(UniquePasswordPolicy.is_in_use())


class TestTrimAllPasswordHistories(TestCase):
    """Test the task that trims password history for all users"""

    def setUp(self):
        self.user1 = create_test_user("test-user1")
        self.user2 = create_test_user("test-user2")
        self.pbm = PolicyBindingModel.objects.create()
        # Create a policy with a limit of 1 password
        self.policy = UniquePasswordPolicy.objects.create(num_historical_passwords=1)
        PolicyBinding.objects.create(
            target=self.pbm,
            policy=self.policy,
            enabled=True,
            order=0,
        )


class TestCheckAndPurgePasswordHistory(TestCase):
    """Test the scheduled task that checks if any policy is in use and purges if not"""

    def setUp(self):
        self.user = create_test_user("test-user")
        self.pbm = PolicyBindingModel.objects.create()

    def test_purge_when_no_policy_in_use(self):
        """Test that the task purges the table when no policy is in use"""
        # Create some password history entries
        UserPasswordHistory.create_for_user(self.user, "hunter2")

        # Verify we have entries
        self.assertTrue(UserPasswordHistory.objects.exists())

        # Run the task - should purge since no policy is in use
        check_and_purge_password_history()

        # Verify the table is empty
        self.assertFalse(UserPasswordHistory.objects.exists())

    def test_no_purge_when_policy_in_use(self):
        """Test that the task doesn't purge when a policy is in use"""
        # Create a policy and binding
        policy = UniquePasswordPolicy.objects.create(num_historical_passwords=5)
        PolicyBinding.objects.create(
            target=self.pbm,
            policy=policy,
            enabled=True,
            order=0,
        )

        # Create some password history entries
        UserPasswordHistory.create_for_user(self.user, "hunter2")

        # Verify we have entries
        self.assertTrue(UserPasswordHistory.objects.exists())

        # Run the task - should NOT purge since a policy is in use
        check_and_purge_password_history()

        # Verify the entries still exist
        self.assertTrue(UserPasswordHistory.objects.exists())


class TestTrimPasswordHistory(TestCase):
    """Test password history cleanup task"""

    def setUp(self):
        self.user = create_test_user("test-user")
        self.pbm = PolicyBindingModel.objects.create()

    def test_trim_password_history_ok(self):
        """Test passwords over the define limit are deleted"""
        _now = datetime.now()
        UserPasswordHistory.objects.bulk_create(
            [
                UserPasswordHistory(
                    user=self.user,
                    old_password="hunter1",  # nosec B106
                    created_at=_now - timedelta(days=3),
                ),
                UserPasswordHistory(
                    user=self.user,
                    old_password="hunter2",  # nosec B106
                    created_at=_now - timedelta(days=2),
                ),
                UserPasswordHistory(
                    user=self.user,
                    old_password="hunter3",  # nosec B106
                    created_at=_now,
                ),
            ]
        )

        policy = UniquePasswordPolicy.objects.create(num_historical_passwords=1)
        PolicyBinding.objects.create(
            target=self.pbm,
            policy=policy,
            enabled=True,
            order=0,
        )
        trim_password_histories.delay()
        user_pwd_history_qs = UserPasswordHistory.objects.filter(user=self.user)
        self.assertEqual(len(user_pwd_history_qs), 1)

    def test_trim_password_history_policy_diabled_no_op(self):
        """Test no passwords removed if policy binding is disabled"""

        # Insert a record to ensure it's not deleted after executing task
        UserPasswordHistory.create_for_user(self.user, "hunter2")

        policy = UniquePasswordPolicy.objects.create(num_historical_passwords=1)
        PolicyBinding.objects.create(
            target=self.pbm,
            policy=policy,
            enabled=False,
            order=0,
        )
        trim_password_histories.delay()
        self.assertTrue(UserPasswordHistory.objects.filter(user=self.user).exists())

    def test_trim_password_history_fewer_records_than_maximum_is_no_op(self):
        """Test no passwords deleted if fewer passwords exist than limit"""

        UserPasswordHistory.create_for_user(self.user, "hunter2")

        policy = UniquePasswordPolicy.objects.create(num_historical_passwords=2)
        PolicyBinding.objects.create(
            target=self.pbm,
            policy=policy,
            enabled=True,
            order=0,
        )
        trim_password_histories.delay()
        self.assertTrue(UserPasswordHistory.objects.filter(user=self.user).exists())
