from django.contrib.auth.models import Permission
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import IntegrityError, transaction
from django.test import TestCase
from django.urls import reverse
from apps.inventory.test_usage import setup_usage
from apps.inventory.tests import grant
from apps.access.services import set_role_permissions
from apps.service import test_repair as repair
from .tests import QuotationFixture
from . import queries as q
from .models import ServiceQuotation, CommercialWorkAuthorization


class QuotationSecurityTests(QuotationFixture,TestCase):
    @classmethod
    def setUpTestData(cls): setup_usage(cls)

    def test_cross_company_permission_cannot_manage_or_read_case(self):
        role=grant(self.user,self.other_company)
        set_role_permissions(role=role,permissions=Permission.objects.filter(content_type__app_label="commercial"))
        row=self.create()
        with self.assertRaises(PermissionDenied): self.update(row,actor=self.user)
        self.assertFalse(q.service_quotations(actor=self.user).exists())

    def test_same_path_permission_scope_cannot_be_combined(self):
        foreign=grant(self.user,self.other_company)
        set_role_permissions(role=foreign,permissions=Permission.objects.filter(content_type__app_label="commercial"))
        local=grant(self.user,self.company,center=self.center)
        set_role_permissions(role=local,permissions=[])
        with self.assertRaises(PermissionDenied): self.create(actor=self.user)

    def test_other_center_permission_does_not_authorize(self):
        role=grant(self.user,self.company,center=self.center2)
        set_role_permissions(role=role,permissions=Permission.objects.filter(content_type__app_label="commercial"))
        with self.assertRaises(PermissionDenied): self.create(actor=self.user)

    def test_manage_permission_does_not_grant_customer_decision(self):
        role=grant(self.user,self.company)
        set_role_permissions(role=role,permissions=Permission.objects.filter(content_type__app_label="commercial",codename="manage_servicequotation"))
        row=self.submit(self.lines(self.create(actor=self.user)))
        with self.assertRaises(PermissionDenied): self.decide(row,actor=self.user)

    def test_inactive_hierarchy_rejects_even_superuser(self):
        from apps.organization.services import deactivate_service_center
        deactivate_service_center(service_center=self.center)
        with self.assertRaises(ValidationError): self.create()

    def test_cross_case_action_and_finding_rejected(self):
        from apps.service.tests import intake
        from apps.service.test_engineer_assignment import assign
        from apps.service import test_diagnosis as diagnosis
        original=self.case
        self.case=intake(self)
        assign(self)
        assessment=diagnosis.begin(self)
        finding=diagnosis.add(self,assessment)
        diagnosis.complete(self,assessment)
        _,action=repair.prepared(self,performed=False)
        self.case=original
        row=self.create()
        for reference in (dict(repair_action=action),dict(finding=finding)):
            with self.subTest(reference=reference),self.assertRaises(ValidationError): self.lines(row,[self.line(**reference)])

    def test_incompatible_variant_only_part_rejected_without_device_variant(self):
        from apps.catalog.models import ProductVariant
        from apps.parts.services import set_spare_part_compatibility
        variant=ProductVariant.objects.filter(product_model=self.model).first()
        self.assertIsNotNone(variant)
        set_spare_part_compatibility(spare_part=self.part,product_models=[],product_variants=[variant])
        with self.assertRaises(ValidationError): self.lines(self.create(),[self.line(kind="PART",spare_part=self.part)])

    def test_admin_idor_returns_not_found(self):
        row=self.create()
        self.user.is_staff=True
        self.user.save()
        self.user.user_permissions.set(Permission.objects.filter(content_type__app_label="commercial"))
        self.client.force_login(self.user)
        response=self.client.get(reverse("admin:commercial_quotation_workflow",args=[row.pk]))
        self.assertEqual(response.status_code,404)

    def test_customer_snapshot_preserves_display_name(self):
        row=self.submit(self.lines(self.create()))
        self.assertEqual(row.context_snapshot["customer_name"],self.customer.display_name)

    def test_copied_revision_does_not_reset_consumed_quantity_budget(self):
        issue=self.prepared_issue(quantity=2)
        execution,action=repair.prepared(self,performed=False)
        row=self.decide(self.submit(self.lines(self.create(),[self.line(kind="PART",spare_part=self.part,quantity=1)])))
        repair.perform(self,action)
        self.consume(issue,action)
        newer=self.decide(self.submit(self.revise(row)))
        with self.assertRaises(ValidationError): self.consume(issue,action)
        newest=self.lines(self.revise(newer),[self.line(kind="PART",spare_part=self.part,quantity=2)])
        self.decide(self.submit(newest))
        self.consume(issue,action)
        self.assertEqual(CommercialWorkAuthorization.objects.filter(operation="CONSUME").count(),2)

    def test_authorization_evidence_rolls_back_with_failed_consumption(self):
        from unittest.mock import patch
        issue=self.prepared_issue(quantity=1)
        _,action=repair.prepared(self,performed=False)
        self.decide(self.submit(self.lines(self.create(),[self.line(kind="PART",spare_part=self.part)])))
        repair.perform(self,action)
        with patch("apps.inventory.usage_services._post",side_effect=RuntimeError("Injected physical failure")),self.assertRaises(RuntimeError):
            self.consume(issue,action)
        self.assertFalse(CommercialWorkAuthorization.objects.filter(operation="CONSUME").exists())

    def test_database_negative_money_and_invalid_status_rejected(self):
        row=self.create()
        for values in (dict(status="INVALID"),dict(subtotal=-1),dict(revision=0)):
            with self.subTest(values=values),self.assertRaises(IntegrityError),transaction.atomic():
                ServiceQuotation.objects.filter(pk=row.pk).update(**values)
