"""Phase 6B: real scoped reads and service-backed synthetic workflow evidence."""
from datetime import timedelta
from uuid import uuid4
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.db import connection
from django.test import TestCase, Client
from django.test.utils import CaptureQueriesContext
from django.urls import reverse
from apps.access.services import set_role_permissions, create_role_assignment
from apps.organization.assignment_services import create_assignment
from apps.organization.models import Department
from apps.frontdesk import tests as desk_fixture, services as desk
from apps.service import test_quality_control as qc_fixture
from apps.service import test_repair as repair, engineer_services
from apps.service.tests import intake
from apps.inventory import test_usage as inventory_fixture
from apps.commercial import test_payment as payment_fixture
from . import tests as baseline
from .test_ui import capture_page


def section_url(case, section):
    return reverse("operations:job_section", args=[case.pk, section])


def extend_role(role, *names):
    additions = [Permission.objects.get(content_type__app_label=name.split(".")[0], codename=name.split(".")[1]) for name in names]
    set_role_permissions(role=role, permissions=[*role.permissions.all(), *additions])


class JobWorkspaceTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        baseline.WorkspaceTests.setUpTestData.__func__(cls)

    def setUp(self):
        self.client.force_login(self.reader)

    def test_sections_require_their_own_permission_and_do_not_leak_identifiers(self):
        from apps.devices import services as devices
        devices.replace_device_identifier(device=self.device, identifier_type="SERIAL", new_value="SECTION-SECRET-IDENTIFIER")
        set_role_permissions(role=self.role, permissions=[Permission.objects.get(content_type__app_label="service", codename="view_servicecase")])
        for name in ("diagnosis", "delivery", "history"):
            response = self.client.get(section_url(self.case, name))
            self.assertEqual(response.status_code, 200)
            self.assertNotContains(response, "SECTION-SECRET-IDENTIFIER")
            for hidden in ("repair", "parts", "commercial", "qc"):
                self.assertNotContains(response, section_url(self.case, hidden))
        for name in ("repair", "parts", "commercial", "qc"):
            self.assertEqual(self.client.get(section_url(self.case, name)).status_code, 403)

    def test_direct_urls_isolate_company_and_sibling_center(self):
        for case in (self.foreign, self.second_case):
            for name in ("diagnosis", "repair", "parts", "commercial", "qc", "delivery", "history"):
                self.assertEqual(self.client.get(section_url(case, name)).status_code, 404)

    def test_capabilities_on_other_paths_do_not_disclose_sections(self):
        baseline.grant(self.reader, self.company, ["inventory.view_stock", "commercial.view_serviceinvoice", "service.change_servicecase"], self.center2)
        for name in ("parts", "commercial", "repair"):
            self.assertEqual(self.client.get(section_url(self.case, name)).status_code, 403)

    def test_department_only_does_not_become_center_scope(self):
        actor = get_user_model().objects.create_user(username="section-department")
        department = Department.objects.create(company=self.company, code="SECTION", name="Department")
        path = create_assignment(user=actor, company=self.company, department=department)
        create_role_assignment(user=actor, role=self.role, organization_assignment=path)
        self.client.force_login(actor)
        self.assertEqual(self.client.get(section_url(self.case, "history")).status_code, 404)

    def test_readonly_sections_reject_post_and_require_login(self):
        url = section_url(self.case, "history")
        self.assertEqual(self.client.post(url, {}).status_code, 405)
        self.client.logout()
        self.assertEqual(self.client.get(url).status_code, 302)

    def test_next_actions_are_conservative_and_service_backed(self):
        response = self.client.get(reverse("operations:detail", args=["cases", self.case.pk]))
        self.assertContains(response, "Next actions")
        self.assertEqual(response.context["next_actions"], [])
        extend_role(self.role, "service.change_servicecase")
        response = self.client.get(reverse("operations:detail", args=["cases", self.case.pk]))
        self.assertIn(reverse("operations:workflow", args=[self.case.pk, "assignment"]), [url for _, url in response.context["next_actions"]])
        self.assertContains(response, "Review")
        capture_page(response, "job-next-actions")

    def test_history_growth_is_bounded_and_not_a_synthetic_timeline(self):
        extend_role(self.role, "service.handle_servicecase")
        def read():
            with CaptureQueriesContext(connection) as queries:
                response = self.client.get(section_url(self.case, "history"))
            self.assertEqual(response.status_code, 200)
            return response, len(queries)
        # Prime every relevant source so query growth cannot hide behind an empty IN.
        for _ in range(2):
            engineer_services.assign_service_case_engineer(service_case=self.case, engineer=self.reader, assigned_by=self.admin)
            engineer_services.unassign_service_case_engineer(service_case=self.case, unassigned_by=self.admin, reason="History fixture")
        _, before = read()
        for _ in range(11):
            engineer_services.assign_service_case_engineer(service_case=self.case, engineer=self.reader, assigned_by=self.admin)
            engineer_services.unassign_service_case_engineer(service_case=self.case, unassigned_by=self.admin, reason="History fixture")
        response, after = read()
        self.assertEqual(before, after)
        self.assertLessEqual(after, 16)
        group = next(g for g in response.context["groups"] if g["title"] == "Assignment history")
        self.assertEqual(len(group["rows"]), 10)
        self.assertContains(response, "does not establish a global event order")
        print(f"Phase 6B scoped history: {after} queries, constant with assignment history growth")
        capture_page(response, "job-history")


class FrontDeskWorkspaceTests(desk_fixture.Operations, TestCase):
    @classmethod
    def setUpTestData(cls):
        desk_fixture.prepare(cls)
        cls.reader = get_user_model().objects.create_user(username="desk-workspace-reader")
        baseline.grant(cls.reader, cls.company, ["frontdesk.view_appointment", "frontdesk.view_queue", "service.view_servicecase"], cls.center)

    def setUp(self):
        self.client.force_login(self.reader)
        self.url = reverse("frontdesk:desk", args=[self.center.pk])

    def test_appointments_visits_intake_and_recent_jobs_use_actual_evidence(self):
        appointment = self.book()
        checked_in = desk.check_in(actor=self.admin, appointment=appointment)
        self.serving(checked_in)
        case = desk.start_intake(actor=self.admin, queue_entry=checked_in, device=self.device)
        self.walk()
        response = self.client.get(self.url)
        counts = dict(response.context["cards"])
        self.assertEqual((counts["Checked in"], counts["Serving"], counts["Waiting"], counts["Intake created"]), (1, 1, 1, 1))
        self.assertContains(response, reverse("operations:detail", args=["cases", case.pk]))
        self.assertContains(response, "Intake created")
        self.assertEqual(len(response.context["recent_jobs"]), 1)
        self.assertNotContains(response, "Create service intake")
        capture_page(response, "frontdesk-workspace")

    def test_hidden_appointments_and_actions_are_independently_protected(self):
        actor = get_user_model().objects.create_user(username="queue-only")
        baseline.grant(actor, self.company, ["frontdesk.view_queue"], self.center)
        self.client.force_login(actor)
        self.assertNotContains(self.client.get(self.url), "<h2>Appointments</h2>")
        operation = reverse("frontdesk:operation", args=[self.center.pk, "walk-in"])
        self.assertEqual(self.client.get(operation).status_code, 403)
        self.assertEqual(self.client.post(operation, {"customer": self.customer.pk}).status_code, 403)
        for center in (self.center2, self.other_center):
            self.assertEqual(self.client.get(reverse("frontdesk:desk", args=[center.pk])).status_code, 404)

    def test_filters_dates_and_pagination_do_not_mix_counts_or_scopes(self):
        for _ in range(27):
            self.walk()
        response = self.client.get(self.url, {"queue_state": "WAITING"})
        self.assertEqual(len(response.context["queue"]), 25)
        self.assertEqual(dict(response.context["cards"])["Waiting"], 27)
        response = self.client.get(self.url, {"queue_state": "WAITING", "queue_page": 2})
        self.assertEqual(len(response.context["queue"]), 2)
        tomorrow = (self.today + timedelta(days=1)).isoformat()
        response = self.client.get(self.url, {"date": tomorrow})
        self.assertEqual(response.context["queue"], [])
        self.assertEqual(dict(response.context["cards"])["Waiting"], 0)
        self.assertEqual(self.client.get(self.url, {"queue_state": "INTAKE_STARTED"}).status_code, 404)
        self.assertEqual(self.client.get(self.url, {"date": "invalid"}).status_code, 400)

    def test_permission_queries_do_not_grow_per_visit(self):
        self.client.force_login(self.admin)
        self.walk()
        self.book()
        def read():
            with CaptureQueriesContext(connection) as queries:
                response = self.client.get(self.url)
            self.assertEqual(response.status_code, 200)
            return len(queries)
        before = read()
        self.book()
        for _ in range(8):
            self.walk()
        after = read()
        self.assertEqual(before, after)
        self.assertLessEqual(after, 24)
        print(f"Phase 6B front desk: {after} queries, constant with appointment/queue growth")

    def test_confirmation_csrf_prg_and_checkin_replay(self):
        appointment = self.book()
        url = reverse("frontdesk:record-operation", args=[self.center.pk, "check-in", appointment.pk])
        client = Client(enforce_csrf_checks=True)
        client.force_login(self.admin)
        response = client.get(url)
        self.assertContains(response, "Confirm Check in")
        self.assertContains(response, "Cancel and return")
        self.assertContains(response, self.customer.display_name)
        self.assertEqual(client.post(url, {}).status_code, 403)
        data = {"csrfmiddlewaretoken": client.cookies["csrftoken"].value}
        self.assertRedirects(client.post(url, data), self.url, fetch_redirect_response=False)
        self.assertRedirects(client.post(url, data), self.url, fetch_redirect_response=False)
        self.assertEqual(desk_fixture.QueueEntry.objects.filter(appointment=appointment).count(), 1)
        capture_page(response, "frontdesk-confirmation")


class TechnicalWorkspaceTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        qc_fixture.setup_qc(cls)
        extend_role(cls.role, "service.view_servicecase")
        extend_role(cls.qc_role, "service.view_servicecase")

    def test_diagnosis_unknown_cause_and_repair_are_separate_evidence(self):
        self.client.force_login(self.engineer)
        response = self.client.get(section_url(self.case, "diagnosis"))
        self.assertContains(response, "Unknown / Unconfirmed")
        self.assertContains(response, self.fault.name)
        self.assertNotContains(response, "<table")
        capture_page(response, "job-diagnosis")
        response = self.client.get(section_url(self.case, "repair"))
        self.assertContains(response, self.action_type.name)
        self.assertContains(response, "REPAIRED")
        capture_page(response, "job-repair")

    def test_engineer_queue_requires_active_assignment(self):
        self.client.force_login(self.engineer)
        response = self.client.get("/workspace/cases/?bucket=mine&phase=repair")
        self.assertEqual(len(response.context["cards"]), 1)
        self.assertContains(response, 'name="bucket" value="mine"')
        facts = dict(response.context["cards"][0]["facts"])
        self.assertEqual(facts["Latest diagnosis"], "Completed")
        self.assertEqual(facts["Repair outcome"], "REPAIRED")
        self.assertContains(response, self.symptom.name)
        capture_page(response, "engineer-queue")
        self.client.force_login(self.engineer2)
        self.assertEqual(self.client.get("/workspace/cases/?bucket=mine").context["cards"], [])
        self.assertEqual(self.client.get(reverse("operations:workflow", args=[self.case.pk, "repair"])).status_code, 403)

    def test_qc_independence_and_current_state_filters(self):
        extend_role(self.role, "service.perform_quality_control")
        self.client.force_login(self.engineer)
        self.assertEqual(self.client.get("/workspace/cases/?bucket=qc").context["cards"], [])
        self.assertEqual(self.client.get(reverse("operations:workflow", args=[self.case.pk, "qc"])).status_code, 403)
        self.client.force_login(self.inspector)
        response = self.client.get("/workspace/cases/?bucket=qc&phase=pending")
        self.assertEqual(len(response.context["cards"]), 1)
        row = qc_fixture.prepared(self, filled=True)
        self.assertEqual(len(self.client.get("/workspace/cases/?bucket=qc&phase=in-progress").context["cards"]), 1)
        qc_fixture.pass_qc(self, row)
        response = self.client.get("/workspace/cases/?bucket=qc&phase=passed")
        self.assertEqual(len(response.context["cards"]), 1)
        capture_page(response, "qc-queue")
        with CaptureQueriesContext(connection) as queries:
            response = self.client.get(section_url(self.case, "qc"))
        self.assertContains(response, "PASSED")
        self.assertContains(response, "QC technical checks")
        self.assertContains(response, "RESOLVED")
        print(f"Phase 6B QC section: {len(queries)} queries")
        self.assertLessEqual(len(queries), 12)
        capture_page(response, "job-qc")

    def test_qc_failure_history_does_not_bypass_inspector_scope(self):
        row = qc_fixture.prepared(self)
        qc_fixture.check(self, row, result="FAIL")
        qc_fixture.fail_qc(self, row)
        self.client.force_login(self.inspector)
        response = self.client.get("/workspace/cases/?bucket=qc&phase=rework")
        self.assertEqual(len(response.context["cards"]), 1)
        self.assertContains(response, "Recorded QC failures are history")
        self.client.force_login(self.engineer)
        self.assertEqual(self.client.get("/workspace/cases/?bucket=mine&phase=rework").context["cards"], [])
        self.assertEqual(self.client.get(section_url(self.case, "qc")).status_code, 403)
        self.assertNotContains(self.client.get(section_url(self.case, "history")), "Synthetic failure")

    def test_queue_and_technical_sections_have_bounded_queries(self):
        self.client.force_login(self.engineer)
        def measure(url):
            with CaptureQueriesContext(connection) as queries:
                response = self.client.get(url)
            self.assertEqual(response.status_code, 200)
            return len(queries)
        url = "/workspace/cases/?bucket=mine"
        before = measure(url)
        for _ in range(5):
            case = intake(self)
            engineer_services.assign_service_case_engineer(service_case=case, engineer=self.engineer, assigned_by=self.user)
        after = measure(url)
        self.assertEqual(before, after)
        self.assertLessEqual(after, 8)
        print(f"Phase 6B engineer queue: {after} queries, constant with queue growth")
        for name, budget in (("diagnosis", 12), ("repair", 11)):
            count = measure(section_url(self.case, name))
            print(f"Phase 6B {name} section: {count} queries")
            self.assertLessEqual(count, budget)

    def test_qc_queue_queries_do_not_grow_with_eligible_jobs(self):
        from types import SimpleNamespace
        from apps.service import test_diagnosis as diagnosis
        self.client.force_login(self.inspector)
        def read():
            with CaptureQueriesContext(connection) as queries:
                response = self.client.get("/workspace/cases/?bucket=qc&phase=pending")
            self.assertEqual(response.status_code, 200)
            return response, len(queries)
        first, before = read()
        self.assertEqual(len(first.context["cards"]), 1)
        for _ in range(5):
            fixture = SimpleNamespace(case=intake(self), engineer=self.engineer,
                user=self.user, fault=self.fault, action_type=self.action_type)
            engineer_services.assign_service_case_engineer(service_case=fixture.case, engineer=self.engineer, assigned_by=self.user)
            assessment = diagnosis.begin(fixture)
            diagnosis.add(fixture, assessment)
            diagnosis.complete(fixture, assessment)
            execution, _ = repair.prepared(fixture)
            repair.complete(fixture, execution)
        response, after = read()
        self.assertEqual(len(response.context["cards"]), 6)
        self.assertEqual(before, after)
        self.assertLessEqual(after, 8)
        print(f"Phase 6B.1 QC queue growth: {before} -> {after} queries (1 -> 6 jobs)")
        capture_page(response, "qc-queue-growth")


class PartsJobWorkspaceTests(inventory_fixture.UsageFixture, TestCase):
    @classmethod
    def setUpTestData(cls):
        inventory_fixture.setup_usage(cls)
        cls.viewer = get_user_model().objects.create_user(username="job-parts-reader")
        baseline.grant(cls.viewer, cls.company, ["service.view_servicecase", "inventory.view_stock"], cls.center)

    def test_parts_activity_uses_ledger_evidence_without_stock_inference(self):
        issue = self.prepared_issue()
        _, action = repair.prepared(self)
        consumed = self.consume(issue, action)
        self.return_unused(issue)
        from apps.inventory import services as stock, usage_services
        location = stock.create_location(actor=self.actor, company=self.company, service_center=self.center,
            code="REMOVED", name="Removed components", location_type="DEFECTIVE")
        usage_services.recover_defective_component(actor=self.engineer, repair_action=action, location=location,
            component_description="Removed display", observed_identifier="PRIVATE-REMOVED-SERIAL",
            replacement=consumed, command_key=uuid4())
        self.client.force_login(self.viewer)
        with CaptureQueriesContext(connection) as queries:
            response = self.client.get(section_url(self.case, "parts"))
        self.assertContains(response, self.part.part_code)
        self.assertContains(response, "ISSUED")
        self.assertContains(response, "Consumed")
        self.assertContains(response, "Returned")
        self.assertContains(response, "Removed display")
        self.assertNotContains(response, "PRIVATE-REMOVED-SERIAL")
        self.assertNotContains(response, "Waiting for Parts")
        self.assertNotContains(response, "<table")
        self.assertLessEqual(len(queries), 15)
        print(f"Phase 6B parts section: {len(queries)} queries")
        capture_page(response, "job-parts")

    def test_parts_query_growth_and_parent_limit(self):
        self.prepared_issue()
        self.client.force_login(self.viewer)
        def read():
            with CaptureQueriesContext(connection) as queries:
                response = self.client.get(section_url(self.case, "parts"))
            self.assertEqual(response.status_code, 200)
            return response, len(queries)
        _, before = read()
        for _ in range(10):
            self.prepared_issue()
        response, after = read()
        self.assertEqual(before, after)
        self.assertLessEqual(after, 15)
        groups = {group["title"]: group["rows"] for group in response.context["groups"]}
        for title in ("Parts requests", "Reservations", "Issued parts"):
            self.assertEqual(len(groups[title]), 10)
        self.assertEqual(len(groups["Requested quantities"]), 10)
        print(f"Phase 6B.1 parts growth: {before} -> {after} queries (1 -> 11 issues; preview capped at 10)")
        capture_page(response, "job-parts-growth")


class CommercialJobWorkspaceTests(payment_fixture.PaymentFixture, TestCase):
    @classmethod
    def setUpTestData(cls):
        payment_fixture.shared_payment_data(cls)
        cls.viewer = get_user_model().objects.create_user(username="job-commercial-reader")
        cls.read_role = baseline.grant(cls.viewer, cls.company, ["service.view_servicecase", "commercial.view_servicequotation", "commercial.view_serviceinvoice", "commercial.view_servicepayment"], cls.center)

    def setUp(self):
        self.client.force_login(self.viewer)

    def test_due_release_is_not_payment_and_approval_is_evidence(self):
        self.release_due()
        with CaptureQueriesContext(connection) as queries:
            response = self.client.get(section_url(self.case, "commercial"))
        self.assertContains(response, "Authorized due-release; balance remains due")
        self.assertContains(response, "APPROVED")
        self.assertContains(response, "Customer decision")
        self.assertContains(response, "Invoice value (not revenue)")
        groups = {group["title"]: group["rows"] for group in response.context["groups"]}
        self.assertEqual(dict(groups["Financial clearance"][0])["Outstanding balance"], 100)
        self.assertEqual(dict(groups["Financial clearance"][0])["Paid"], 0)
        self.assertLessEqual(len(queries), 13)
        print(f"Phase 6B commercial section: {len(queries)} queries")
        capture_page(response, "job-commercial")
        self.pay()
        response = self.client.get(section_url(self.case, "delivery"))
        self.assertContains(response, "No outstanding customer balance")
        capture_page(response, "job-delivery")

    def test_invoice_permission_does_not_disclose_payment_or_quote(self):
        set_role_permissions(role=self.read_role, permissions=[Permission.objects.get(content_type__app_label=app, codename=code)
            for app, code in (("service", "view_servicecase"), ("commercial", "view_serviceinvoice"))])
        response = self.client.get(section_url(self.case, "commercial"))
        self.assertContains(response, self.invoice.number)
        self.assertNotContains(response, "Quotation revisions")
        self.assertNotContains(response, "Financial clearance")
        self.assertNotContains(response, "<h2>Payments</h2>")

    def test_populated_history_is_bounded_and_revocation_is_fresh(self):
        extend_role(self.read_role, "service.change_servicecase", "service.perform_quality_control", "inventory.view_stock")
        self.pay(amount="10")
        def read():
            with CaptureQueriesContext(connection) as queries:
                response = self.client.get(section_url(self.case, "history"))
            self.assertEqual(response.status_code, 200)
            return response, len(queries)
        _, before = read()
        for _ in range(5):
            self.pay(amount="10")
        response, after = read()
        self.assertEqual(before, after)
        self.assertLessEqual(after, 32)
        for label in ("Diagnostic findings", "Repair actions", "Issued parts", "QC attempts", "Payments", "Delivery evidence"):
            self.assertContains(response, label)
        print(f"Phase 6B populated history: {after} queries, constant with payment history growth")
        capture_page(response, "job-full-history")
        set_role_permissions(role=self.read_role, permissions=[Permission.objects.get(content_type__app_label="service", codename="view_servicecase")])
        response = self.client.get(section_url(self.case, "history"))
        for label in ("Diagnostic findings", "Repair actions", "Issued parts", "QC attempts", "Payments"):
            self.assertNotContains(response, label)

    def test_commercial_query_growth_and_payment_limit(self):
        self.pay(amount="1")
        def read():
            with CaptureQueriesContext(connection) as queries:
                response = self.client.get(section_url(self.case, "commercial"))
            self.assertEqual(response.status_code, 200)
            return response, len(queries)
        _, before = read()
        for _ in range(10):
            self.pay(amount="1")
        response, after = read()
        self.assertEqual(before, after)
        self.assertLessEqual(after, 13)
        groups = {group["title"]: group["rows"] for group in response.context["groups"]}
        self.assertEqual(len(groups["Payments"]), 10)
        self.assertEqual(dict(groups["Financial clearance"][0])["Paid"], 11)
        self.assertEqual(dict(groups["Financial clearance"][0])["Outstanding balance"], 89)
        print(f"Phase 6B.1 commercial growth: {before} -> {after} queries (1 -> 11 payments; preview capped at 10)")
        capture_page(response, "job-commercial-growth")
