from django.contrib.auth.models import Permission
from django.test import Client
from django.urls import reverse

from . import document_services as d
from .models import GoodsReceipt, SerializedStockUnit, StockMovement, StockTransfer
from .tests import grant
from .test_documents import DocumentFixture, revision


class DocumentAdminTests(DocumentFixture):
    def setUp(self):
        self.client.force_login(self.actor)

    def form_data(self, *, transfer=False):
        data = dict(destination=str(self.destination.pk if transfer else self.location.pk), note="Admin note", _save="Save",
            **{"lines-TOTAL_FORMS": "1", "lines-INITIAL_FORMS": "0", "lines-MIN_NUM_FORMS": "0", "lines-MAX_NUM_FORMS": "1000",
               "lines-0-spare_part": str(self.serial_part.pk if not transfer else self.part.pk), "lines-0-quantity": "1"})
        if transfer:
            data["source"] = str(self.location.pk)
            data["lines-0-selected_units"] = []
        else:
            data["external_reference"] = "External document"
            data["lines-0-serial_identifiers"] = "ADMIN-SERIAL"
        return data

    def confirm(self, document, action):
        model = "goodsreceipt" if isinstance(document, GoodsReceipt) else "stocktransfer"
        url = reverse(f"admin:inventory_{model}_changelist")
        data = dict(action=action, _selected_action=[str(document.pk)])
        page = self.client.post(url, data)
        self.assertEqual(page.status_code, 200)
        return url, data | dict(confirm_transition="1", transition_revision=page.context["transition_revision"])

    def test_receipt_creation_with_lines_is_service_backed_and_draft_only(self):
        response = self.client.post(reverse("admin:inventory_goodsreceipt_add"), self.form_data())
        self.assertEqual(response.status_code, 302)
        row = GoodsReceipt.objects.get()
        self.assertEqual(row.status, "DRAFT")
        self.assertEqual(row.lines.get().identifiers.get().identifier, "ADMIN-SERIAL")
        self.assertEqual(SerializedStockUnit.objects.count(), 0)

    def test_transfer_creation_with_lines(self):
        response = self.client.post(reverse("admin:inventory_stocktransfer_add"), self.form_data(transfer=True))
        self.assertEqual(response.status_code, 302)
        self.assertEqual(StockTransfer.objects.get().lines.get().quantity, 1)

    def test_post_requires_review_and_signed_confirmation(self):
        row = self.receipt()
        url, data = self.confirm(row, "post_selected")
        self.assertEqual(StockMovement.objects.count(), 0)
        self.assertEqual(self.client.post(url, data).status_code, 302)
        row.refresh_from_db()
        self.assertEqual(row.status, "POSTED")

    def test_stale_confirmation_rejected(self):
        row = self.receipt()
        url, data = self.confirm(row, "post_selected")
        d.update_goods_receipt(actor=self.actor, receipt=row, expected_revision=revision(row), note="Changed")
        response = self.client.post(url, data, follow=True)
        self.assertContains(response, "Transition rejected")
        self.assertEqual(StockMovement.objects.count(), 0)

    def test_tampered_confirmation_rejected(self):
        row = self.receipt()
        url, data = self.confirm(row, "post_selected")
        data["transition_revision"] = "forged"
        response = self.client.post(url, data, follow=True)
        self.assertContains(response, "Transition rejected")
        self.assertEqual(StockMovement.objects.count(), 0)

    def test_serialized_posted_receipt_admin_is_readonly(self):
        row = self.post(self.receipt(1, part=self.serial_part, identifiers=["READONLY"]))
        url = reverse("admin:inventory_goodsreceipt_change", args=[row.pk])
        self.assertContains(self.client.get(url), "READONLY")
        self.assertEqual(self.client.post(url, {"note": "Changed"}).status_code, 403)
        self.assertEqual(self.client.post(reverse("admin:inventory_goodsreceipt_delete", args=[row.pk]), {"post": "yes"}).status_code, 403)

    def test_transfer_dispatch_and_receipt_confirmations(self):
        self.post(self.receipt())
        row = self.transfer()
        for action, status in (("dispatch_selected", "DISPATCHED"), ("receive_selected", "RECEIVED")):
            url, data = self.confirm(row, action)
            self.assertEqual(self.client.post(url, data).status_code, 302)
            row.refresh_from_db()
            self.assertEqual(row.status, status)

    def test_cancel_confirmation_requires_reason(self):
        row = self.receipt()
        url, data = self.confirm(row, "cancel_selected")
        self.client.post(url, data)
        row.refresh_from_db()
        self.assertEqual(row.status, "DRAFT")
        data["reason"] = "Not delivered"
        self.client.post(url, data)
        row.refresh_from_db()
        self.assertEqual(row.status, "CANCELLED")

    def test_csrf_on_confirmation(self):
        row = self.receipt()
        url, data = self.confirm(row, "post_selected")
        client = Client(enforce_csrf_checks=True)
        client.force_login(self.actor)
        self.assertEqual(client.post(url, data).status_code, 403)

    def test_document_cross_company_idor(self):
        row = self.receipt(destination=self.foreign)
        grant(self.staff, self.company)
        self.staff.user_permissions.set(Permission.objects.filter(content_type__app_label="inventory"))
        self.client.force_login(self.staff)
        self.assertEqual(self.client.get(reverse("admin:inventory_goodsreceipt_change", args=[row.pk])).status_code, 302)
        response = self.client.post(reverse("admin:inventory_goodsreceipt_changelist"), dict(action="post_selected", _selected_action=[str(row.pk)]))
        self.assertEqual(response.status_code, 302)
        self.assertEqual(StockMovement.objects.count(), 0)

    def test_confirmation_note_and_references_are_escaped(self):
        row = d.create_goods_receipt(actor=self.actor, destination=self.location, note="<script>alert(1)</script>")
        response = self.client.get(reverse("admin:inventory_goodsreceipt_change", args=[row.pk]))
        self.assertNotContains(response, "<script>alert(1)</script>")
