from apps.crm.models import Customer
from apps.invoicing.models import Invoice
from apps.modules import services as modules
from domain.exceptions import DomainError
from tests.base import CoreTestCase

URL = "/api/v1/invoicing/invoices/"


class InvoicingTests(CoreTestCase):
    def setUp(self):
        super().setUp()
        for code in ("crm", "invoicing"):
            modules.enable_module(actor=self.root, organization=self.org_a, module_code=code)
        self.mgr = self.api(self.manager_a, self.org_a)
        self.emp = self.api(self.employee_a, self.org_a)
        self.customer = Customer.objects.create(organization=self.org_a, name="Acme", status="active", created_by=self.manager_a, owner=self.manager_a)

    def body(self, **extra):
        data = {
            "customer": self.customer.pk, "issue_date": "2025-01-10", "tax_percent": "10", "discount": 50_000,
            "items": [
                {"description": "Service", "quantity": "2", "unit_price": 500_000},
                {"description": "Part", "quantity": "1.5", "unit_price": 100_000},
            ],
        }
        data.update(extra)
        return data

    def make(self, client=None, **extra):
        r = (client or self.mgr).post(URL, self.body(**extra), format="json")
        self.assertEqual(r.status_code, 201, r.data)
        return r.data

    def test_totals(self):
        inv = self.make()
        # lines 1,000,000 + 150,000 = 1,150,000; discount 50,000; tax 10% of 1,100,000 = 110,000
        self.assertEqual((inv["subtotal"], inv["tax"], inv["total"]), (1_150_000, 110_000, 1_210_000))
        self.assertEqual((inv["status"], inv["number"]), ("draft", None))

    def test_validation(self):
        bad = lambda **kw: self.mgr.post(URL, self.body(**kw), format="json").status_code
        self.assertEqual(bad(items=[]), 400)
        self.assertEqual(bad(discount=9_999_999), 400)
        self.assertEqual(bad(tax_percent="150"), 400)
        self.assertEqual(bad(items=[{"description": " ", "quantity": "1", "unit_price": 1}]), 400)
        self.assertEqual(bad(items=[{"description": "x", "quantity": "0", "unit_price": 1}]), 400)
        self.assertEqual(bad(customer=99999), 400)

    def test_numbers_are_sequential_per_organization(self):
        a, b = self.make(), self.make()
        self.assertEqual(self.mgr.post(f"{URL}{a['id']}/issue/").data["number"], 1)
        self.assertEqual(self.mgr.post(f"{URL}{b['id']}/issue/").data["number"], 2)
        modules.enable_module(actor=self.root, organization=self.org_b, module_code="crm")
        modules.enable_module(actor=self.root, organization=self.org_b, module_code="invoicing")
        cust_b = Customer.objects.create(organization=self.org_b, name="B", created_by=self.manager_b)
        mb = self.api(self.manager_b, self.org_b)
        pk = mb.post(URL, self.body(customer=cust_b.pk), format="json").data["id"]
        self.assertEqual(mb.post(f"{URL}{pk}/issue/").data["number"], 1)  # independent counter

    def test_lifecycle_rules(self):
        inv = self.make()
        pk = inv["id"]
        self.assertEqual(self.mgr.post(f"{URL}{pk}/pay/").status_code, 400)  # not issued yet
        self.assertEqual(self.mgr.post(f"{URL}{pk}/cancel/").status_code, 400)
        self.mgr.post(f"{URL}{pk}/issue/")
        self.assertEqual(self.mgr.post(f"{URL}{pk}/issue/").status_code, 400)
        self.assertEqual(self.mgr.patch(f"{URL}{pk}/", {"notes": "x"}, format="json").status_code, 400)  # locked
        self.assertEqual(self.mgr.delete(f"{URL}{pk}/").status_code, 400)
        self.assertEqual(self.mgr.post(f"{URL}{pk}/pay/").data["status"], "paid")
        self.assertEqual(self.mgr.post(f"{URL}{pk}/cancel/").status_code, 400)  # paid cannot be cancelled

    def test_cancel_keeps_number_and_record(self):
        pk = self.make()["id"]
        self.mgr.post(f"{URL}{pk}/issue/")
        r = self.mgr.post(f"{URL}{pk}/cancel/")
        self.assertEqual((r.data["status"], r.data["number"]), ("cancelled", 1))
        self.assertEqual(Invoice.objects.count(), 1)

    def test_draft_edit_recomputes_and_delete(self):
        pk = self.make()["id"]
        r = self.mgr.patch(f"{URL}{pk}/", {"discount": 0}, format="json")
        self.assertEqual(r.data["total"], 1_265_000)  # tax 10% of 1,150,000 = 115,000
        r = self.mgr.patch(f"{URL}{pk}/", {"items": [{"description": "Only", "quantity": "1", "unit_price": 1_000}], "tax_percent": "0"}, format="json")
        self.assertEqual((len(r.data["items"]), r.data["total"]), (1, 1_000))
        self.assertEqual(self.mgr.delete(f"{URL}{pk}/").status_code, 204)

    def test_employee_has_no_invoicing_access_by_default(self):
        self.assertEqual(self.emp.get(URL).status_code, 403)
        self.assertEqual(self.emp.post(URL, self.body(), format="json").status_code, 403)

    def test_self_scope_user_sees_only_own_invoices_and_cannot_issue(self):
        # give the employee create/view/update at self scope only
        from apps.access.models import Permission, RolePermission

        role = self.role(self.org_a, "employee")
        for code in ("invoicing.view", "invoicing.create", "invoicing.update", "crm.view"):
            RolePermission.objects.update_or_create(role=role, permission=Permission.objects.get(code=code), defaults={"scope": "self"})
        mine_customer = Customer.objects.create(organization=self.org_a, name="Mine", status="active", created_by=self.employee_a, owner=self.employee_a)
        own = self.emp.post(URL, self.body(customer=mine_customer.pk), format="json")
        self.assertEqual(own.status_code, 201)
        boss = self.make()
        ids = [i["id"] for i in self.emp.get(URL).data["results"]]
        self.assertEqual(ids, [own.data["id"]])
        self.assertEqual(self.emp.get(f"{URL}{boss['id']}/").status_code, 404)
        self.assertEqual(self.emp.post(self.body and f"{URL}{own.data['id']}/issue/").status_code, 403)
        # a customer owned by someone else cannot be billed
        self.assertEqual(self.emp.post(URL, self.body(), format="json").status_code, 400)

    def test_tenant_isolation_and_module_gate(self):
        pk = self.make()["id"]
        self.assertEqual(self.api(self.manager_b, self.org_b).get(URL).status_code, 403)  # module off in B
        for code in ("crm", "invoicing"):
            modules.enable_module(actor=self.root, organization=self.org_b, module_code=code)
        other = self.api(self.manager_b, self.org_b)
        self.assertEqual(other.get(f"{URL}{pk}/").status_code, 404)
        self.assertEqual(other.post(f"{URL}{pk}/issue/").status_code, 404)
        self.assertEqual(other.post(URL, self.body(), format="json").status_code, 400)  # customer of org A
        modules.disable_module(actor=self.root, organization=self.org_a, module_code="invoicing")
        self.assertEqual(self.mgr.get(URL).status_code, 403)
        self.assertEqual(Invoice.objects.count(), 1)

    def test_summary_search_and_customer_choices(self):
        a = self.make()
        self.make()
        self.mgr.post(f"{URL}{a['id']}/issue/")
        s = self.mgr.get("/api/v1/invoicing/summary/").data
        self.assertEqual((s["draft"]["count"], s["issued"]["count"], s["issued"]["total"]), (1, 1, 1_210_000))
        self.assertEqual(self.mgr.get(URL, {"search": "1"}).data["count"], 1)  # by number
        self.assertEqual(self.mgr.get(URL, {"status": "draft"}).data["count"], 1)
        self.assertEqual([c["name"] for c in self.mgr.get("/api/v1/invoicing/customers/").data], ["Acme"])

    def test_invoicing_needs_crm(self):
        with self.assertRaises(DomainError):
            modules.enable_module(actor=self.root, organization=self.org_b, module_code="invoicing")
