from datetime import timedelta

from django.contrib.auth.models import Group
from django.core import mail
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone

from accounts.forms import UserCreateForm
from accounts.models import Employee, GroupCapability, User
from core.permissions import Capability


class UserCapabilityTests(TestCase):
    def test_legacy_owner_maps_to_super_admin_permissions(self):
        user = User.objects.create_user(
            username='legacy-owner',
            password='testpass123',
            role=User.Role.OWNER,
        )
        self.assertTrue(user.is_owner)
        self.assertTrue(user.has_capability(Capability.USERS_MANAGE))
        self.assertTrue(user.has_capability(Capability.INVENTORY_PRICE_UPDATE))

    def test_storekeeper_cannot_edit_prices(self):
        user = User.objects.create_user(
            username='storekeeper',
            password='testpass123',
            role=User.Role.STOREKEEPER,
        )
        self.assertFalse(user.has_capability(Capability.INVENTORY_PRICE_UPDATE))
        self.assertTrue(user.has_capability(Capability.INVENTORY_STOCK_ADJUST))

    def test_accountant_can_manage_invoice_payments_but_not_stock_adjust(self):
        user = User.objects.create_user(
            username='accountant',
            password='testpass123',
            role=User.Role.ACCOUNTANT,
        )
        self.assertTrue(user.has_capability(Capability.INVOICE_PAYMENT_MANAGE))
        self.assertFalse(user.has_capability(Capability.INVENTORY_STOCK_ADJUST))

    def test_group_capability_grants_user_access(self):
        user = User.objects.create_user(
            username="custom-capability-user",
            password="testpass123",
            role=User.Role.SALES_OFFICER,
        )
        group = Group.objects.create(name="Custom Access")
        GroupCapability.objects.create(group=group, capability=Capability.AUDIT_VIEW)
        user.groups.add(group)

        self.assertTrue(user.has_capability(Capability.AUDIT_VIEW))
        self.assertTrue(user.has_any_capability(Capability.AUDIT_VIEW, Capability.FINANCE_DELETE))

    def test_user_is_added_to_managed_role_group(self):
        user = User.objects.create_user(
            username="role-group-user",
            password="testpass123",
            role=User.Role.STOREKEEPER,
        )

        self.assertTrue(user.groups.filter(name="role:storekeeper").exists())


class AuthenticationFlowTests(TestCase):
    def test_failed_login_attempts_lock_account(self):
        user = User.objects.create_user(
            username="lockable",
            password="testpass123",
            role=User.Role.SALES_OFFICER,
        )

        login_url = reverse("accounts:login")
        for _ in range(5):
            self.client.post(login_url, {"username": "lockable", "password": "wrongpass"})

        user.refresh_from_db()
        self.assertTrue(user.is_locked)
        self.assertEqual(user.failed_login_attempts, 0)
        self.assertGreater(user.locked_until, timezone.now())

    def test_login_redirects_to_profile_when_password_change_required(self):
        user = User.objects.create_user(
            username="must-change",
            password="testpass123",
            role=User.Role.SALES_OFFICER,
            must_change_password=True,
        )

        response = self.client.post(
            reverse("accounts:login"),
            {"username": user.username, "password": "testpass123"},
        )

        self.assertRedirects(response, reverse("accounts:profile"))


class UserManagementSecurityTests(TestCase):
    def test_user_toggle_requires_post(self):
        admin_user = User.objects.create_user(
            username="admin-user",
            password="testpass123",
            role=User.Role.SUPER_ADMIN,
        )
        target = User.objects.create_user(
            username="target-user",
            password="testpass123",
            role=User.Role.SALES_OFFICER,
        )
        self.client.force_login(admin_user)

        response = self.client.get(reverse("accounts:user_toggle", args=[target.pk]))
        self.assertEqual(response.status_code, 405)

    def test_locked_user_cannot_login_until_lock_expires(self):
        user = User.objects.create_user(
            username="already-locked",
            password="testpass123",
            role=User.Role.ACCOUNTANT,
            locked_until=timezone.now() + timedelta(minutes=10),
        )

        response = self.client.post(
            reverse("accounts:login"),
            {"username": user.username, "password": "testpass123"},
        )

        self.assertContains(response, "temporarily locked", status_code=200)


class UserProvisioningTests(TestCase):
    def test_user_create_form_can_link_employee_and_apply_temporary_password(self):
        employee = Employee.objects.create(
            code="EMP-001",
            first_name="Jane",
            last_name="Doe",
            email="jane@example.com",
            phone="0700000000",
            role=User.Role.STOREKEEPER,
            is_active=True,
        )

        form = UserCreateForm(
            data={
                "employee": employee.pk,
                "role": User.Role.STOREKEEPER,
                "is_active": "on",
                "default_password": "TempPass#123A",
            }
        )

        self.assertTrue(form.is_valid(), form.errors)
        user = form.save()
        employee.refresh_from_db()

        self.assertEqual(employee.user_id, user.id)
        self.assertTrue(user.username)
        self.assertTrue(user.check_password("TempPass#123A"))
        self.assertTrue(user.must_change_password)

    def test_user_create_form_can_generate_temporary_password(self):
        form = UserCreateForm(
            data={
                "role": User.Role.SALES_OFFICER,
                "is_active": "on",
                "generate_password": "on",
            }
        )

        self.assertTrue(form.is_valid(), form.errors)
        user = form.save()

        self.assertTrue(form.created_password)
        self.assertTrue(user.username)
        self.assertTrue(user.check_password(form.created_password))
        self.assertTrue(user.must_change_password)

    def test_user_create_form_uses_default_password_133_when_blank(self):
        form = UserCreateForm(
            data={
                "role": User.Role.SALES_OFFICER,
                "is_active": "on",
            }
        )

        self.assertTrue(form.is_valid(), form.errors)
        user = form.save()

        self.assertEqual(form.created_password, "133")
        self.assertTrue(user.check_password("133"))
        self.assertTrue(user.must_change_password)


class CredentialSlipTests(TestCase):
    def setUp(self):
        self.admin_user = User.objects.create_user(
            username="admin-creator",
            password="testpass123",
            role=User.Role.SUPER_ADMIN,
            email="admin@example.com",
        )
        self.client.force_login(self.admin_user)

    def test_user_create_redirects_to_credentials_slip(self):
        response = self.client.post(
            reverse("accounts:user_create"),
            {
                "first_name": "New",
                "last_name": "Staff",
                "role": User.Role.SALES_OFFICER,
                "is_active": "on",
                "default_password": "TempPass#123A",
            },
        )

        created_user = User.objects.get(first_name="New", last_name="Staff")
        self.assertTrue(created_user.username.startswith("newstaff"))
        self.assertRedirects(response, reverse("accounts:user_credentials", args=[created_user.pk]))

        slip_response = self.client.get(reverse("accounts:user_credentials", args=[created_user.pk]))
        self.assertContains(slip_response, "TempPass#123A")

    def test_credential_slip_can_send_email(self):
        user = User.objects.create_user(
            username="email-user",
            password="testpass123",
            role=User.Role.SALES_OFFICER,
            email="staff@example.com",
        )

        session = self.client.session
        session["provisioned_credentials"] = {
            str(user.pk): {
                "username": user.username,
                "temporary_password": "TempPass#123A",
                "created_at_ts": timezone.now().timestamp(),
            }
        }
        session.save()

        self.client.post(
            reverse("accounts:user_credentials", args=[user.pk]),
            {"action": "send_email"},
        )

        self.assertEqual(len(mail.outbox), 1)
        self.assertIn("email-user", mail.outbox[0].body)
        self.assertIn("TempPass#123A", mail.outbox[0].body)
