import re

from django.core.management.base import BaseCommand
from django.db import transaction

from apps.complaints.models import Complaint
from apps.organizations.models import (
    Department,
    LegacyHierarchyMapping,
    LegacyMainSection,
    LegacySubSection,
    Section,
)


def _normalize(s):
    if not s:
        return ""
    s = s.lower().strip()
    for suffix in [
        " clinics",
        " clinic",
        " department",
        " dept",
        " ward",
        " unit",
        " services",
    ]:
        s = s.replace(suffix, "")
    s = re.sub(r"\s+", " ", s).strip()
    return s


MANUAL_MAP = {
    2: "Internal Medicine",
    5: "Internal Medicine",
    6: "Internal Medicine",
    9: "Internal Medicine",
    11: "Internal Medicine",
    13: "Internal Medicine",
    14: "Internal Medicine",
    15: "Surgeries",
    18: "Surgeries",
    20: "Surgeries",
    21: "Surgeries",
    22: "Surgeries",
    23: "Surgeries",
    32: "Surgeries",
    34: "Emergency Department",
    35: "Critical Care",
    36: "Pediatric Department",
    37: "Pediatric Department",
    38: "Critical Care",
    39: "Pediatric Department",
    40: "Inpatient Department",
    41: "Inpatient Department",
    42: "Inpatient Department",
    43: "Anesthesia & OR Department",
    44: "Anesthesia & OR Department",
    24: "Surgeries",
    97: "Patient Relations & Patient Experience Department",
    45: "Surgeries",
    46: "Inpatient Department",
    47: "Obstetrics & Gynecology",
    48: "Inpatient Department",
    50: "Medical Ancillary Services",
    51: "Medical Ancillary Services",
    53: "Pharmacy",
    55: "Medical Ancillary Services",
    56: "Medical Ancillary Services",
    57: "Medical Ancillary Services",
    58: "Medical Ancillary Services",
    59: "Medical Ancillary Services",
    60: "Medical Ancillary Services",
    61: "Medical Ancillary Services",
    62: "Surgeries",
    78: "Nursing Department",
    79: "Nursing Department",
    88: "Outpatient Department",
    89: "Corporate Administration",
    90: "Finance Department",
    92: "Outpatient Department",
    94: "Outpatient Department",
    95: "Security Department",
    96: "Emergency Department",
    98: "Emergency Department",
    99: "Outpatient Department",
    100: "Emergency Department",
    102: "Medical Records Department",
    103: "Outpatient Department",
    104: "Medical Approvals Department",
    105: "Security Department",
    109: "Nursing Department",
    110: "Nursing Department",
    111: "Security Department",
    113: "Outpatient Department",
    147: "Internal Medicine",
    149: "Housekeeping & Hospitality Department",
    151: "Nursing Department",
    152: "Nursing Department",
    153: "Nursing Department",
    155: "Nursing Department",
    156: "Nursing Department",
    157: "Nursing Department",
    158: "Nursing Department",
    159: "Nursing Department",
    160: "Nursing Department",
    161: "Nursing Department",
    162: "Nursing Department",
    163: "Nursing Department",
    164: "Food Services Department",
    165: "Housekeeping & Hospitality Department",
    167: "Outpatient Department",
    168: "Corporate Administration",
    169: "Finance Department",
    170: "Patient Relations & Patient Experience Department",
    171: "Patient Affairs Department",
    172: "Inpatient Department",
    173: "Medical Approvals Department",
    174: "Executive Administration",
    176: "Obstetrics & Gynecology",
    177: "Nursing Department",
    178: "Nursing Department",
    180: "Laboratory",
    182: "Nursing Department",
    183: "Nursing Department",
    184: "Patient Affairs Department",
    185: "Nursing Department",
    186: "Radiology",
    187: "Medical Ancillary Services",
    188: "Internal Medicine",
    189: "Pediatric Department",
    190: "Pediatric Department",
    191: "Pediatric Department",
    192: "Pediatric Department",
    193: "Surgeries",
    194: "Surgeries",
    195: "Pediatric Department",
    196: "Pediatric Department",
    197: "Surgeries",
    198: "Obstetrics & Gynecology",
    199: "Internal Medicine",
    200: "Internal Medicine",
    201: "Medical Ancillary Services",
    202: "Pediatric Department",
    203: "Corporate Administration",
    205: "Pharmacy",
    206: "Medical Ancillary Services",
    207: "Facility Management & Maintenance",
    208: "Internal Medicine",
    209: "Pediatric Department",
    210: "Emergency Department",
    211: "Laboratory",
    212: "Surgeries",
    213: "Information Technology",
    215: "Outpatient Department",
    216: "Critical Care",
    219: "Laboratory",
    220: "Emergency Department",
    222: "Information Technology",
    223: "Outpatient Department",
    224: "Information Technology",
    225: "Medical Ancillary Services",
    226: "Medical Records Department",
    227: "Medical Ancillary Services",
    228: "Medical Records Department",
    229: "Inpatient Department",
    230: "Finance Department",
}


class Command(BaseCommand):
    help = "Backfill complaint.department from legacy subsection hierarchy"

    def add_arguments(self, parser):
        parser.add_argument(
            "--dry-run",
            action="store_true",
            help="Show what would be updated without making changes",
        )
        parser.add_argument(
            "--backfill-section",
            action="store_true",
            help="Also backfill complaint.section from LegacyHierarchyMapping",
        )
        parser.add_argument(
            "--model",
            type=str,
            default="complaint",
            choices=["complaint"],
            help="Model to backfill (default: complaint)",
        )

    def handle(self, *args, **options):
        dry_run = options["dry_run"]
        backfill_section = options["backfill_section"]

        self._build_lookup_tables()

        if backfill_section:
            self._backfill_sections(dry_run)
            return

        complaints = Complaint.objects.filter(
            department__isnull=True,
            legacy_subsection__isnull=False,
        ).select_related("legacy_subsection", "legacy_main_section")

        self.stdout.write(f"Complaints to backfill: {complaints.count()}")

        matched = 0
        unmatched = 0
        unmatched_details = {}

        with transaction.atomic():
            for complaint in complaints:
                dept = self._resolve_department(complaint)
                if dept:
                    matched += 1
                    if not dry_run:
                        complaint.department = dept
                        complaint.save(update_fields=["department"])
                else:
                    unmatched += 1
                    ss = complaint.legacy_subsection
                    key = (ss.name_en, ss.main_section.name_en if ss.main_section else "?")
                    unmatched_details[key] = unmatched_details.get(key, 0) + 1

            if dry_run:
                transaction.set_rollback(True)

        self.stdout.write(self.style.SUCCESS(f"\nMatched: {matched}"))
        self.stdout.write(self.style.WARNING(f"Unmatched: {unmatched}"))

        if unmatched_details:
            self.stdout.write("\nUnmatched subsections:")
            for (name, ms), cnt in sorted(
                unmatched_details.items(), key=lambda x: -x[1]
            ):
                self.stdout.write(f"  {cnt:4d} | {ms[:15]:15s} | {name[:50]}")

    def _build_lookup_tables(self):
        self.dept_by_name = {}
        for d in Department.objects.all():
            self.dept_by_name[d.name_en.lower().strip()] = d

        self.dept_by_old_name = {}
        self.section_by_old_name = {}
        for m in LegacyHierarchyMapping.objects.select_related("main_section", "subsection"):
            key = m.old_subsection_en.lower().strip()
            if key and m.main_section:
                self.dept_by_old_name[key] = m.main_section
            if key and m.subsection:
                self.section_by_old_name[key] = m.subsection

        self.norm_dept_names = {}
        for name, dept in self.dept_by_name.items():
            self.norm_dept_names[_normalize(name)] = dept

        self.stdout.write(
            f"Lookup tables: {len(self.dept_by_name)} depts, "
            f"{len(self.dept_by_old_name)} legacy mappings, "
            f"{len(self.section_by_old_name)} section mappings"
        )

    def _backfill_sections(self, dry_run):
        """Backfill complaint.section from LegacyHierarchyMapping."""
        complaints = Complaint.objects.filter(
            section__isnull=True,
            legacy_subsection__isnull=False,
        ).select_related("legacy_subsection")

        self.stdout.write(f"Complaints to backfill section: {complaints.count()}")

        matched = 0
        unmatched = 0

        with transaction.atomic():
            for complaint in complaints:
                section = self._resolve_section(complaint)
                if section:
                    matched += 1
                    if not dry_run:
                        complaint.section = section
                        complaint.save(update_fields=["section"])
                else:
                    unmatched += 1

            if dry_run:
                transaction.set_rollback(True)

        self.stdout.write(self.style.SUCCESS(f"\nSection matched: {matched}"))
        self.stdout.write(self.style.WARNING(f"Section unmatched: {unmatched}"))

    def _resolve_section(self, complaint):
        """Resolve section from legacy subsection via LegacyHierarchyMapping."""
        ss = complaint.legacy_subsection
        if not ss:
            return None

        # 1. Exact match via LegacyHierarchyMapping.subsection
        ss_name = ss.name_en.lower().strip()
        if ss_name in self.section_by_old_name:
            return self.section_by_old_name[ss_name]

        # 2. Try to find section by name within the complaint's department
        if complaint.department:
            section = Section.objects.filter(
                department=complaint.department,
                name_en__iexact=ss.name_en,
            ).first()
            if section:
                return section

            # Normalized match
            norm_ss = _normalize(ss.name_en)
            for sec in Section.objects.filter(department=complaint.department):
                if _normalize(sec.name_en) == norm_ss:
                    return sec

        return None

    def _resolve_department(self, complaint):
        ss = complaint.legacy_subsection
        if not ss:
            return None

        # 1. Manual override map (highest priority)
        pk = ss.pk
        if pk in MANUAL_MAP:
            dept_name = MANUAL_MAP[pk]
            return self.dept_by_name.get(dept_name.lower().strip())

        # 2. Exact match via LegacyHierarchyMapping
        ss_name = ss.name_en.lower().strip()
        if ss_name in self.dept_by_old_name:
            return self.dept_by_old_name[ss_name]

        # 3. Exact name match to new Department
        if ss_name in self.dept_by_name:
            return self.dept_by_name[ss_name]

        # 4. Normalized match
        norm_ss = _normalize(ss.name_en)
        if norm_ss in self.norm_dept_names:
            return self.norm_dept_names[norm_ss]

        # 5. Contains match (one name contains the other)
        for norm_dept, dept in self.norm_dept_names.items():
            if len(norm_ss) > 5 and (
                norm_ss in norm_dept or norm_dept in norm_ss
            ):
                if abs(len(norm_ss) - len(norm_dept)) < 15:
                    return dept

        return None
