from datetime import datetime, timedelta

from django.contrib.auth import get_user_model
from django.db import connection
from django.test import TestCase
from django.test.utils import CaptureQueriesContext
from django.utils import timezone

from apps.service import tests as intake_fixture
from apps.service.services import add_service_case_complaint, cancel_service_case
from apps.service.models import ServiceCase
from apps.service_catalog.models import ComplaintSymptomProductCategory
from apps.catalog.models import ProductCategory
from apps.reporting.filters import ReportFilters, period
from apps.reporting.scope import cases
from apps.reporting.permissions import OPERATIONAL
from apps.reporting.service_dashboard import aging, dashboard_tables
from apps.reporting.service_analytics import complaint_frequency
from .test_reports import grant_report


class BoundaryTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        intake_fixture.setup(cls)
        cls.reader = get_user_model().objects.create_superuser(username="boundary-reader")
        cls.instant = timezone.make_aware(datetime(2025, 9, 30, 12))
        cls.case = intake_fixture.intake(cls, received_at=cls.instant)
        add_service_case_complaint(service_case=cls.case, complaint_symptom=cls.symptom)

    def test_inclusive_last_local_day_half_open_next_day(self):
        day = timezone.make_aware(datetime(2025, 9, 30))
        at_start = intake_fixture.intake(self, received_at=day)
        last = intake_fixture.intake(self, received_at=day + timedelta(days=1) - timedelta(microseconds=1))
        after = intake_fixture.intake(self, received_at=day + timedelta(days=1))
        form = ReportFilters({"date_from": "2025-09-30", "date_to": "2025-09-30"})
        self.assertTrue(form.is_valid())
        ids = set(period(ServiceCase.objects.all(), "received_at", form.cleaned_data).values_list("pk", flat=True))
        self.assertIn(at_start.pk, ids)
        self.assertIn(last.pk, ids)
        self.assertNotIn(after.pk, ids)

    def test_elapsed_aging_exact_boundaries(self):
        expected = {0: "0–1 day", 1: "0–1 day", 2: "2–3 days", 3: "2–3 days", 4: "4–7 days", 7: "4–7 days", 8: "8–14 days", 14: "8–14 days", 15: "15–30 days", 30: "15–30 days", 31: "31+ days"}
        for days, label in expected.items():
            row = aging(ServiceCase.objects.filter(pk=self.case.pk), "received_at", now=self.instant + timedelta(days=days)).get()
            self.assertEqual(row.age_bucket, label)

    def test_cancelled_excluded_from_open_aging(self):
        cancel_service_case(service_case=self.case, cancelled_by=self.user, reason="Synthetic cancellation")
        report = next(t for t in dashboard_tables(self.reader, {}) if t.key == "aging")
        self.assertEqual(list(report.rows), [])

    def test_applicability_mappings_do_not_classify_intake_complaints(self):
        alternate = ProductCategory.objects.create(code="OTHER-CATEGORY", name="Other category")
        from apps.service_catalog.services import set_complaint_applicability
        set_complaint_applicability(complaint=self.symptom, applies_to_all_product_categories=False, product_categories=[self.category, alternate])
        rows = complaint_frequency(self.reader, {}, dimension="product_category")
        self.assertEqual(rows.get()["dimension_id"], self.category.pk)
        self.assertNotIn("servicecategory", str(rows.query).lower())

    def test_empty_authorized_scope_and_center_filter(self):
        self.assertFalse(cases(self.reader, OPERATIONAL, {"service_center": self.center2.pk}).exists())
        self.client.force_login(self.reader)
        response = self.client.get("/reports/", {"service_center": str(self.center2.pk)})
        self.assertContains(response, "No authorized records match")

    def test_wrong_center_and_region(self):
        from apps.organization.models import Region
        region = Region.objects.create(company=self.company, code="ANOTHER", name="Another")
        for name, kwargs in (("wrong-center", {"center": self.center2}), ("wrong-region", {"region": region})):
            user = get_user_model().objects.create_user(username=name)
            grant_report(user, self.company, **kwargs)
            self.assertFalse(cases(user, OPERATIONAL, {}).exists())

    def test_center_department_retains_frozen_scope_semantics(self):
        from apps.organization.models import Department
        department = Department.objects.create(company=self.company, code="REPORT-DEPT", name="Reporting department")
        user = get_user_model().objects.create_user(username="center-department")
        grant_report(user, self.company, center=self.center, department=department)
        self.assertTrue(cases(user, OPERATIONAL, {}).exists())

    def test_stable_pagination_and_constant_dashboard_and_complaint_queries(self):
        self.client.force_login(self.reader)
        urls = (("/reports/", {"table": "cases"}), ("/reports/service/", {"table": "complaints_complaint"}))
        before = []
        for url, params in urls:
            with CaptureQueriesContext(connection) as captured:
                self.assertEqual(self.client.get(url, params).status_code, 200)
            before.append(len(captured))
        for _ in range(51):
            row = intake_fixture.intake(self, received_at=self.instant)
            add_service_case_complaint(service_case=row, complaint_symptom=self.symptom)
        for index, (url, params) in enumerate(urls):
            with CaptureQueriesContext(connection) as captured:
                self.assertEqual(self.client.get(url, params).status_code, 200)
            self.assertEqual(len(captured), before[index])
        first = self.client.get("/reports/", {"page": 1}).context["rows"]
        second = self.client.get("/reports/", {"page": 2}).context["rows"]
        self.assertEqual((len(first), len(second)), (50, 2))
        ids = [row[0] for row in first + second]
        self.assertEqual(ids, sorted(ids))
        self.assertEqual(len(set(ids)), 52)
