import pytest
from django.contrib.auth import get_user_model
from datetime import date, timedelta, datetime
from rest_framework.test import APIClient
from apps.administrations.models import (
    CarBooking, Reimbursement, PaymentRequest, DocumentRequest,
    LetterNumber, LetterPrefix, Announcement, SOP, InternalGuide, HelpdeskTicket
)
from apps.companies.models import Organization, OrganizationMembership


@pytest.fixture
def organization(db):
    return Organization.objects.create(name="Test Org")


@pytest.fixture
def user(db, organization):
    User = get_user_model()
    u = User.objects.create_user(email="admin@example.com", password="pass123")
    organization.members.add(u)
    return u


@pytest.fixture
def other_user(db, organization):
    from apps.hr.signals import ensure_employee_stub
    User = get_user_model()
    u = User.objects.create_user(email="admin-other@example.com", password="pass123")
    OrganizationMembership.objects.create(organization=organization, user=u)
    ensure_employee_stub(u)
    return u


@pytest.fixture
def full_scope_user(db, organization):
    """User with full administrations scope (view/update/delete) — required
    by IsOwnerOrFullScope/OwnRequestScopedMixin to act on someone else's
    request, and to see it in get_queryset at all."""
    from apps.core.models import Permission, Role, UserRole
    from apps.hr.signals import ensure_employee_stub
    User = get_user_model()
    manager = User.objects.create_user(email="admin-manager@example.com", password="pass123")
    OrganizationMembership.objects.create(organization=organization, user=manager)
    ensure_employee_stub(manager)
    role = Role.objects.create(name="Administrations Manager", slug="administrations-manager-test")
    perm, _ = Permission.objects.get_or_create(domain="administrations", action="*", defaults={"label": "All administrations"})
    role.permissions.add(perm)
    UserRole.objects.create(user=manager, role=role, organization=organization)
    return manager


def _client_for(user):
    client = APIClient()
    client.force_authenticate(user=user)
    return client


@pytest.mark.django_db
class TestCarBookingModel:
    def test_create_car_booking(self, user, organization):
        booking = CarBooking.objects.create(
            organization=organization,
            requester=user,
            vehicle_name="Toyota Avanza",
            purpose="Client meeting",
            start_datetime=datetime.now(),
            end_datetime=datetime.now() + timedelta(hours=4),
            destination="Client Office"
        )
        assert booking.status == "pending"


@pytest.mark.django_db
class TestReimbursementModel:
    def test_create_reimbursement(self, user, organization):
        reimb = Reimbursement.objects.create(
            organization=organization,
            requester=user,
            title="Travel Expenses",
            description="Flight and hotel",
            amount=1500000
        )
        assert reimb.status == "pending"


@pytest.mark.django_db
class TestAdministrationWorkflowSecurity:
    def test_approved_by_is_server_owned(self, user, full_scope_user, organization):
        User = get_user_model()
        foreign_organization = Organization.objects.create(name="Foreign Approver Org")
        foreign_user = User.objects.create_user(
            email="foreign-approver@example.com", password="pass123"
        )
        OrganizationMembership.objects.create(
            organization=foreign_organization, user=foreign_user
        )
        reimbursement = Reimbursement.objects.create(
            organization=organization,
            requester=user,
            title="Travel",
            amount=100000,
        )

        response = _client_for(full_scope_user).patch(
            f"/api/reimbursements/{reimbursement.pk}/",
            {"approved_by": str(foreign_user.pk)},
            format="json",
        )

        assert response.status_code == 200
        reimbursement.refresh_from_db()
        assert reimbursement.approved_by is None

    def test_delete_scope_does_not_grant_workflow_decision(self, user, organization):
        from apps.core.models import Permission, Role, UserRole

        User = get_user_model()
        manager = User.objects.create_user(email="records-manager@example.com", password="pass123")
        OrganizationMembership.objects.create(organization=organization, user=manager)
        role = Role.objects.create(name="Records Manager", slug="records-manager-security-test")
        for action in ("update", "delete"):
            permission, _ = Permission.objects.get_or_create(
                domain="administrations",
                action=action,
                defaults={"label": f"Administrations {action}"},
            )
            role.permissions.add(permission)
        UserRole.objects.create(user=manager, role=role, organization=organization)
        reimbursement = Reimbursement.objects.create(
            organization=organization,
            requester=user,
            title="Travel",
            amount=100000,
        )

        response = _client_for(manager).patch(
            f"/api/reimbursements/{reimbursement.pk}/",
            {"status": "approved"},
            format="json",
        )

        assert response.status_code == 400
        reimbursement.refresh_from_db()
        assert reimbursement.status == "pending"


@pytest.mark.django_db
class TestPaymentRequestModel:
    def test_create_payment_request(self, user, organization):
        pr = PaymentRequest.objects.create(
            organization=organization,
            requester=user,
            title="Vendor Payment",
            description="Pay vendor for services",
            amount=5000000,
            payee="Vendor XYZ"
        )
        assert pr.status == "pending"


@pytest.mark.django_db
class TestDocumentRequestModel:
    def test_create_document_request(self, user, organization):
        doc_req = DocumentRequest.objects.create(
            organization=organization,
            requester=user,
            document_type="Tax Certificate",
            purpose="For visa application"
        )
        assert doc_req.status == "pending"


@pytest.mark.django_db
class TestLetterNumberModel:
    def test_create_letter_number(self, organization):
        prefix = LetterPrefix.objects.create(organization=organization, code="SURAT", label="Surat Umum")
        letter = LetterNumber.objects.create(
            organization=organization,
            prefix=prefix,
            year=2026,
            month=4,
            sequence=1,
            subject="Meeting Minutes",
            recipient="All Staff",
            sender="Management",
            date=date.today()
        )
        # <seq 3-digit>/<prefix code>/<roman month>/<year>
        assert letter.full_number == "001/SURAT/IV/2026"


@pytest.mark.django_db
class TestAnnouncementModel:
    def test_create_announcement(self, user, organization):
        ann = Announcement.objects.create(
            organization=organization,
            title="Office Closure",
            content="Office will be closed on Friday",
            author=user,
            valid_from=datetime.now()
        )
        assert ann.priority == "info"


@pytest.mark.django_db
class TestSOPModel:
    def test_create_sop(self, user, organization):
        sop = SOP.objects.create(
            organization=organization,
            code="SOP-HR-001",
            title="Leave Policy",
            category="HR",
            content="Leave policy details...",
            effective_date=date.today(),
            author=user
        )
        assert sop.version == "1.0"


@pytest.mark.django_db
class TestInternalGuideModel:
    def test_create_internal_guide(self, user, organization):
        guide = InternalGuide.objects.create(
            organization=organization,
            title="Onboarding Guide",
            category="HR",
            content="Welcome to the company...",
            author=user
        )
        assert guide.category == "HR"


@pytest.mark.django_db
class TestHelpdeskAssignmentNotify:
    def test_assignment_notifies_new_assignee(self, user, other_user, full_scope_user, organization):
        from apps.notifications.models import Notification
        ticket = HelpdeskTicket.objects.create(organization=organization, requester=user, subject="Laptop broken", description="D")
        client = _client_for(full_scope_user)
        resp = client.patch(f"/api/helpdesk-tickets/{ticket.id}/", {"assigned_to": str(other_user.id)}, format="json")
        assert resp.status_code == 200
        assert Notification.objects.filter(user=other_user, notification_type="administrations").exists()

    def test_no_notify_on_noop_update(self, user, other_user, full_scope_user, organization):
        from apps.notifications.models import Notification
        ticket = HelpdeskTicket.objects.create(organization=organization, requester=user, subject="Laptop broken", description="D", assigned_to=other_user)
        client = _client_for(full_scope_user)
        resp = client.patch(f"/api/helpdesk-tickets/{ticket.id}/", {"subject": "Laptop still broken"}, format="json")
        assert resp.status_code == 200
        assert not Notification.objects.filter(user=other_user, notification_type="administrations").exists()

    def test_no_self_notify_on_self_assign(self, user, full_scope_user, organization):
        from apps.notifications.models import Notification
        ticket = HelpdeskTicket.objects.create(organization=organization, requester=user, subject="Laptop broken", description="D")
        client = _client_for(full_scope_user)
        resp = client.patch(f"/api/helpdesk-tickets/{ticket.id}/", {"assigned_to": str(full_scope_user.id)}, format="json")
        assert resp.status_code == 200
        assert not Notification.objects.filter(user=full_scope_user, notification_type="administrations").exists()

    def test_permission_denied_without_scope(self, user, other_user, organization):
        ticket = HelpdeskTicket.objects.create(organization=organization, requester=user, subject="Laptop broken", description="D")
        client = _client_for(other_user)
        resp = client.patch(f"/api/helpdesk-tickets/{ticket.id}/", {"assigned_to": str(other_user.id)}, format="json")
        assert resp.status_code in (403, 404)


@pytest.mark.django_db
class TestStatusChangeNotify:
    def test_car_booking_approved_notifies_requester(self, user, full_scope_user, organization):
        from apps.notifications.models import Notification
        booking = CarBooking.objects.create(
            organization=organization,
            requester=user, vehicle_name="Avanza", purpose="Meeting",
            start_datetime=datetime.now(), end_datetime=datetime.now() + timedelta(hours=4),
            destination="Client Office",
        )
        client = _client_for(full_scope_user)
        resp = client.patch(f"/api/car-bookings/{booking.id}/", {"status": "approved"}, format="json")
        assert resp.status_code == 200
        assert Notification.objects.filter(user=user, notification_type="administrations").exists()

    def test_reimbursement_rejected_notifies_requester(self, user, full_scope_user, organization):
        from apps.notifications.models import Notification
        reimb = Reimbursement.objects.create(organization=organization, requester=user, title="Travel", description="D", amount=100000)
        client = _client_for(full_scope_user)
        resp = client.patch(f"/api/reimbursements/{reimb.id}/", {"status": "rejected"}, format="json")
        assert resp.status_code == 200
        assert Notification.objects.filter(user=user, notification_type="administrations").exists()

    def test_payment_request_approved_notifies_requester(self, user, full_scope_user, organization):
        from apps.notifications.models import Notification
        pr = PaymentRequest.objects.create(organization=organization, requester=user, title="Vendor", description="D", amount=100000, payee="Vendor")
        client = _client_for(full_scope_user)
        resp = client.patch(f"/api/payment-requests/{pr.id}/", {"status": "approved"}, format="json")
        assert resp.status_code == 200
        assert Notification.objects.filter(user=user, notification_type="administrations").exists()

    def test_no_notify_when_status_unchanged(self, user, full_scope_user, organization):
        from apps.notifications.models import Notification
        reimb = Reimbursement.objects.create(organization=organization, requester=user, title="Travel", description="D", amount=100000)
        client = _client_for(full_scope_user)
        resp = client.patch(f"/api/reimbursements/{reimb.id}/", {"description": "Updated"}, format="json")
        assert resp.status_code == 200
        assert not Notification.objects.filter(user=user, notification_type="administrations").exists()


@pytest.mark.django_db
class TestAnnouncementBroadcast:
    def test_broadcasts_to_active_employees(self, other_user, full_scope_user):
        from apps.notifications.models import Notification
        from apps.hr.models import Employee
        for u in (other_user, full_scope_user):
            emp = Employee.objects.get(user=u)
            emp.status = "active"
            emp.save()
        client = _client_for(full_scope_user)
        resp = client.post("/api/announcements/", {
            "title": "Office closure", "content": "Closed Friday", "valid_from": datetime.now().isoformat(),
            "author": str(full_scope_user.id),
        }, format="json")
        assert resp.status_code == 201
        # author excluded, other_user notified
        assert not Notification.objects.filter(user=full_scope_user, notification_type="administrations").exists()
        assert Notification.objects.filter(user=other_user, notification_type="administrations").exists()
