"""
Tests for the unified reference-number generator (apps.core.reference).

Covers: hospital-token sanitization, format, per-module prefix coverage,
sequence monotonicity, None-hospital fallback, and concurrent allocation
uniqueness.
"""

import threading
from datetime import datetime
from unittest.mock import patch

from django.test import TestCase, TransactionTestCase

from apps.core.models import ReferenceSequence
from apps.core.reference import generate_reference, sanitize_hospital_token
from apps.organizations.models import Hospital, Organization


def _make_hospital(code="TEST-HOSP"):
    org, _ = Organization.objects.get_or_create(code="TEST-ORG", defaults={"name": "Test Org", "status": "active"})
    hospital, _ = Hospital.objects.get_or_create(
        code=code,
        defaults={"organization": org, "name": f"Test {code}", "status": "active"},
    )
    return hospital


class SanitizeTokenTest(TestCase):
    def test_strips_hyphens_and_uppercases(self):
        class H:
            def __init__(self, code):
                self.code = code

        self.assertEqual(sanitize_hospital_token(H("HH-N")), "HHN")
        self.assertEqual(sanitize_hospital_token(H("e2e-hosp")), "E2EHOSP")
        self.assertEqual(sanitize_hospital_token(H("main-campus-1")), "MAINCAMPUS1")

    def test_none_or_empty_hospital(self):
        self.assertEqual(sanitize_hospital_token(None), "GEN")
        self.assertEqual(sanitize_hospital_token(object()), "GEN")

        class Empty:
            code = ""

        self.assertEqual(sanitize_hospital_token(Empty()), "GEN")

    def test_strips_non_alphanumeric(self):
        class H:
            def __init__(self, code):
                self.code = code

        self.assertEqual(sanitize_hospital_token(H("NUZHA@2026")), "NUZHA2026")
        self.assertEqual(sanitize_hospital_token(H("  spaced  ")), "SPACED")


class GenerateReferenceFormatTest(TestCase):
    def setUp(self):
        self.hospital = _make_hospital("HH-N")

    def test_format_and_prefix(self):
        with patch("apps.core.reference.datetime") as mock_dt:
            mock_dt.now.return_value = datetime(2026, 6, 14)
            for prefix in ("CMP", "INQ", "OBS", "APR", "SGT"):
                ref = generate_reference(prefix, self.hospital)
                self.assertRegex(
                    ref,
                    rf"^{prefix}-202606-HHN-0001$",
                    f"unexpected ref {ref} for prefix {prefix}",
                )

    def test_sequence_increments_monotonically(self):
        refs = [generate_reference("CMP", self.hospital) for _ in range(5)]
        self.assertEqual(refs, [f"CMP-202606-HHN-{i:04d}" for i in range(1, 6)])
        # all unique
        self.assertEqual(len(set(refs)), len(refs))

    def test_each_module_has_independent_sequence(self):
        with patch("apps.core.reference.datetime") as mock_dt:
            mock_dt.now.return_value = datetime(2026, 6, 14)
            cmp1 = generate_reference("CMP", self.hospital)
            inq1 = generate_reference("INQ", self.hospital)
            cmp2 = generate_reference("CMP", self.hospital)
        self.assertIn("-0001", cmp1)
        self.assertIn("-0001", inq1)  # independent sequence
        self.assertIn("-0002", cmp2)

    def test_per_hospital_isolation(self):
        h2 = _make_hospital("HH-A")
        with patch("apps.core.reference.datetime") as mock_dt:
            mock_dt.now.return_value = datetime(2026, 6, 14)
            ref_n = generate_reference("CMP", self.hospital)  # HHN
            ref_a = generate_reference("CMP", h2)  # HHA
        self.assertIn("-HHN-", ref_n)
        self.assertIn("-HHA-", ref_a)
        self.assertIn("-0001", ref_n)
        self.assertIn("-0001", ref_a)  # separate counter per hospital

    def test_none_hospital_fallback(self):
        ref = generate_reference("CMP", None)
        self.assertRegex(ref, r"^CMP-\d{6}-GEN-0001$")

    def test_sequence_resets_per_month(self):
        with patch("apps.core.reference.datetime") as mock_dt:
            mock_dt.now.return_value = datetime(2026, 6, 14)
            generate_reference("CMP", self.hospital)
            generate_reference("CMP", self.hospital)  # 202606 -> 2
            mock_dt.now.return_value = datetime(2026, 7, 1)
            july_ref = generate_reference("CMP", self.hospital)
        self.assertIn("-202607-HHN-0001", july_ref)


class ConcurrencyTest(TransactionTestCase):
    """Concurrent allocation must never produce duplicate sequence numbers."""

    def test_concurrent_allocations_are_unique(self):
        hospital = _make_hospital("HH-S")
        n = 40
        results = [None] * n
        barrier = threading.Barrier(n)

        def worker(idx):
            barrier.wait()
            results[idx] = generate_reference("CMP", hospital)

        threads = [threading.Thread(target=worker, args=(i,)) for i in range(n)]
        for t in threads:
            t.start()
        for t in threads:
            t.join(timeout=30)

        self.assertNotIn(None, results, "a worker did not produce a reference")
        self.assertEqual(len(set(results)), n, "duplicate references allocated under concurrency")
        # numbers should be exactly 1..n
        numbers = sorted(int(r.split("-")[-1]) for r in results)
        self.assertEqual(numbers, list(range(1, n + 1)))
        # one sequence row for this key
        self.assertEqual(ReferenceSequence.objects.filter(prefix="CMP", hospital_token="HHS").count(), 1)
