"""
Doctor Rating Adapter Service

Handles the transformation of Doctor Rating data from HIS/CSV to internal format.
- Parses doctor names (extracts ID prefix like '10738-')
- Matches doctors to existing Staff records
- Creates individual ratings and aggregates monthly
"""

import logging
import re
from datetime import datetime
from typing import Dict, List, Optional, Tuple

from django.db import transaction
from django.utils import timezone

from apps.organizations.models import Hospital, Patient, Staff

from .models import DoctorRatingImportJob, PhysicianIndividualRating, PhysicianMonthlyRating

logger = logging.getLogger(__name__)


class DoctorRatingAdapter:
    """
    Adapter for transforming Doctor Rating data from HIS/CSV to internal format.
    """

    @staticmethod
    def parse_doctor_name(doctor_name_raw: str) -> Tuple[str, str]:
        """
        Parse doctor name from HIS format.

        HIS Format: "10738-OMAYMAH YAQOUB ELAMEIAN"
        Returns: (doctor_id, doctor_name_clean)

        Examples:
        - "10738-OMAYMAH YAQOUB ELAMEIAN" -> ("10738", "OMAYMAH YAQOUB ELAMEIAN")
        - "OMAYMAH YAQOUB ELAMEIAN" -> ("", "OMAYMAH YAQOUB ELAMEIAN")
        """
        if not doctor_name_raw:
            return "", ""

        doctor_name_raw = doctor_name_raw.strip()

        # Pattern: ID-NAME (e.g., "10738-OMAYMAH YAQOUB ELAMEIAN")
        match = re.match(r"^(\d+)-(.+)$", doctor_name_raw)
        if match:
            doctor_id = match.group(1)
            doctor_name = match.group(2).strip()
            return doctor_id, doctor_name

        # Pattern: ID - NAME (with spaces)
        match = re.match(r"^(\d+)\s*-\s*(.+)$", doctor_name_raw)
        if match:
            doctor_id = match.group(1)
            doctor_name = match.group(2).strip()
            return doctor_id, doctor_name

        # No ID prefix found
        return "", doctor_name_raw

    @staticmethod
    def parse_date(date_str: str) -> Optional[datetime]:
        """
        Parse date from various formats.

        Supported formats:
        - "22-Dec-2024 19:12:24" (HIS format)
        - "22-Dec-2024"
        - "2024-12-22 19:12:24"
        - "2024-12-22"
        - "22/12/2024 19:12:24"
        - "22/12/2024"
        """
        if not date_str:
            return None

        date_str = date_str.strip()

        formats = [
            "%d-%b-%Y %H:%M:%S",
            "%d-%b-%Y %H:%M",
            "%d-%b-%Y",
            "%d-%b-%y %H:%M:%S",
            "%d-%b-%y %H:%M",
            "%d-%b-%y",
            "%Y-%m-%d %H:%M:%S",
            "%Y-%m-%d %H:%M",
            "%Y-%m-%d",
            "%d/%m/%Y %H:%M:%S",
            "%d/%m/%Y %H:%M",
            "%d/%m/%Y",
            "%m/%d/%Y %H:%M:%S",
            "%m/%d/%Y %H:%M",
            "%m/%d/%Y",
        ]

        for fmt in formats:
            try:
                naive_dt = datetime.strptime(date_str, fmt)
                return timezone.make_aware(naive_dt)
            except ValueError:
                continue

        logger.warning(f"Could not parse date: {date_str}")
        return None

    @staticmethod
    def parse_age(age_str: str) -> str:
        """
        Parse age string to extract just the number.

        Examples:
        - "36 Years" -> "36"
        - "36" -> "36"
        """
        if not age_str:
            return ""

        match = re.search(r"(\d+)", age_str)
        if match:
            return match.group(1)
        return age_str

    @staticmethod
    def clean_phone(phone: str) -> str:
        """
        Clean and normalize phone number to international format.

        Examples:
        - "0504884011" -> "+966504884011"
        - "+966504884011" -> "+966504884011"
        """
        if not phone:
            return ""

        phone = phone.strip().replace(" ", "").replace("-", "")

        if phone.startswith("+"):
            return phone

        # Saudi numbers
        if phone.startswith("05"):
            return "+966" + phone[1:]
        elif phone.startswith("5"):
            return "+966" + phone
        elif phone.startswith("0"):
            return "+966" + phone[1:]

        return phone

    @staticmethod
    def find_staff_by_doctor_id(doctor_id: str, hospital: Hospital, doctor_name: str = "") -> Optional[Staff]:
        """
        Find staff record by doctor ID or name.

        Search priority:
        1. Match by employee_id (exact)
        2. Match by license_number (exact)
        3. Match by name (case-insensitive contains)
        """
        if not doctor_id and not doctor_name:
            return None

        # Try by employee_id (exact match)
        if doctor_id:
            staff = Staff.objects.filter(hospital=hospital, employee_id=doctor_id).first()
            if staff:
                return staff

        # Try by license_number
        if doctor_id:
            staff = Staff.objects.filter(hospital=hospital, license_number=doctor_id).first()
            if staff:
                return staff

        # Try by name matching
        if doctor_name:
            # Try exact match first
            staff = Staff.objects.filter(hospital=hospital, name__iexact=doctor_name).first()
            if staff:
                return staff

            # Try contains match on name
            staff = Staff.objects.filter(hospital=hospital, name__icontains=doctor_name).first()
            if staff:
                return staff

            # Try first_name + last_name
            name_parts = doctor_name.split()
            if len(name_parts) >= 2:
                first_name = name_parts[0]
                last_name = name_parts[-1]
                staff = Staff.objects.filter(
                    hospital=hospital, first_name__iexact=first_name, last_name__iexact=last_name
                ).first()
                if staff:
                    return staff

        return None

    @staticmethod
    def process_his_rating_record(data: Dict, hospital: Hospital) -> Dict:
        """
        Process a single doctor rating record from HIS API format.

        HIS Format:
        {
            "DoctorID": "11510",
            "EmpNo": "17046",
            "DoctorName": "AAMIR USMAN BAIG",
            "DoctorDepartment": "ORTHOPAEDIC",
            "HospitalName": "SUWAIDI",
            "HospitalID": "2",
            "Rating": "5.00",
            "RatingDate": "30-Dec-2025 14:06"
        }

        Args:
            data: Dictionary containing HIS rating data
            hospital: Hospital instance

        Returns:
            Dict with 'success', 'rating_id', 'message', 'staff_matched', 'is_duplicate'
        """
        result = {
            "success": False,
            "rating_id": None,
            "message": "",
            "staff_matched": False,
            "staff_id": None,
            "is_duplicate": False,
        }

        try:
            with transaction.atomic():
                # Extract doctor info
                doctor_id = data.get("DoctorID", "").strip()
                emp_no = data.get("EmpNo", "").strip()
                doctor_name = data.get("DoctorName", "").strip()
                department_name = data.get("DoctorDepartment", "").strip()

                # Find staff by DoctorID or EmpNo
                staff = None
                if doctor_id:
                    staff = Staff.objects.filter(hospital=hospital, employee_id=doctor_id).first()

                if not staff and emp_no:
                    staff = Staff.objects.filter(hospital=hospital, employee_id=emp_no).first()

                # Try name matching as fallback
                if not staff and doctor_name:
                    staff = DoctorRatingAdapter.find_staff_by_doctor_id(
                        doctor_id="", hospital=hospital, doctor_name=doctor_name
                    )

                # Mark as physician if matched
                if staff and not staff.physician:
                    staff.physician = True
                    staff.save(update_fields=["physician"])

                # Parse rating date
                rating_date = DoctorRatingAdapter.parse_date(data.get("RatingDate", ""))
                if not rating_date:
                    result["message"] = f"Invalid rating date: {data.get('RatingDate')}"
                    return result

                # Parse rating
                try:
                    rating = int(float(data.get("Rating", 0)))
                    if rating < 1 or rating > 5:
                        result["message"] = f"Invalid rating value: {rating}"
                        return result
                except (ValueError, TypeError):
                    result["message"] = f"Invalid rating format: {data.get('Rating')}"
                    return result

                # Check for duplicates (DoctorID + RatingDate)
                doctor_ref = doctor_id or emp_no
                if doctor_ref:
                    existing = PhysicianIndividualRating.objects.filter(
                        doctor_id=doctor_ref, rating_date=rating_date, hospital=hospital
                    ).first()

                    if existing:
                        result["is_duplicate"] = True
                        result["message"] = f"Duplicate rating for doctor {doctor_ref} on {rating_date}"
                        return result

                # Create individual rating (no patient data for HIS ratings)
                individual_rating = PhysicianIndividualRating.objects.create(
                    staff=staff,
                    hospital=hospital,
                    source=PhysicianIndividualRating.RatingSource.HIS_API,
                    source_reference=f"HIS_{data.get('DoctorID', '')}",
                    doctor_name_raw=doctor_name,
                    doctor_id=doctor_id or emp_no,
                    doctor_name=doctor_name,
                    department_name=department_name,
                    # Patient fields are null for HIS ratings
                    patient_uhid=None,
                    patient_name=None,
                    patient_gender="",
                    patient_age="",
                    patient_nationality="",
                    patient_phone="",
                    patient_type=None,
                    admit_date=None,
                    discharge_date=None,
                    rating=rating,
                    feedback="",  # HIS doesn't provide feedback
                    rating_date=rating_date,
                    is_aggregated=False,
                    metadata={
                        "emp_no": emp_no,
                        "hospital_id_his": data.get("HospitalID", ""),
                        "hospital_name_his": data.get("HospitalName", ""),
                        "imported_at": timezone.now().isoformat(),
                    },
                )

                result["success"] = True
                result["rating_id"] = str(individual_rating.id)
                result["staff_matched"] = staff is not None
                result["staff_id"] = str(staff.id) if staff else None

        except Exception as e:
            logger.error(f"Error processing HIS doctor rating: {str(e)}", exc_info=True)
            result["message"] = str(e)

        return result

    @staticmethod
    def get_or_create_patient(uhid: str, patient_name: str, hospital: Hospital, **kwargs) -> Optional[Patient]:
        """
        Get or create patient by UHID.
        """
        if not uhid:
            return None

        # Split name
        name_parts = patient_name.split() if patient_name else ["Unknown", ""]
        first_name = name_parts[0] if name_parts else "Unknown"
        last_name = name_parts[-1] if len(name_parts) > 1 else ""

        patient, created = Patient.objects.get_or_create(
            mrn=uhid,
            defaults={
                "first_name": first_name,
                "last_name": last_name,
                "primary_hospital": hospital,
            },
        )

        # Update patient info if provided
        if kwargs.get("phone"):
            patient.phone = kwargs["phone"]
        if kwargs.get("nationality"):
            patient.nationality = kwargs["nationality"]
        if kwargs.get("gender"):
            patient.gender = kwargs["gender"].lower()
        if kwargs.get("date_of_birth"):
            patient.date_of_birth = kwargs["date_of_birth"]

        patient.save()
        return patient

    @staticmethod
    def process_single_rating(
        data: Dict,
        hospital: Hospital,
        source: str = PhysicianIndividualRating.RatingSource.HIS_API,
        source_reference: str = "",
    ) -> Dict:
        """
        Process a single doctor rating record.

        Args:
            data: Dictionary containing rating data
            hospital: Hospital instance
            source: Source of the rating (his_api, csv_import, manual)
            source_reference: Reference ID from source system

        Returns:
            Dict with 'success', 'rating_id', 'message', 'staff_matched'
        """
        result = {"success": False, "rating_id": None, "message": "", "staff_matched": False, "staff_id": None}

        try:
            with transaction.atomic():
                # Extract and parse doctor info
                doctor_name_raw = data.get("doctor_name", "").strip()
                doctor_id, doctor_name = DoctorRatingAdapter.parse_doctor_name(doctor_name_raw)

                # Find staff
                staff = DoctorRatingAdapter.find_staff_by_doctor_id(doctor_id, hospital, doctor_name)

                # If staff found, mark as physician
                if staff and not staff.physician:
                    staff.physician = True
                    staff.save(update_fields=["physician"])

                # Extract patient info
                uhid = data.get("uhid", "").strip()
                patient_name = data.get("patient_name", "").strip()

                # Parse dates
                admit_date = DoctorRatingAdapter.parse_date(data.get("admit_date", ""))
                discharge_date = DoctorRatingAdapter.parse_date(data.get("discharge_date", ""))
                rating_date = DoctorRatingAdapter.parse_date(data.get("rating_date", ""))

                if not rating_date and admit_date:
                    rating_date = admit_date

                if not rating_date:
                    rating_date = timezone.now()

                # Clean phone
                phone = DoctorRatingAdapter.clean_phone(data.get("mobile_no", ""))

                # Parse rating
                try:
                    rating = int(float(data.get("rating", 0)))
                    if rating < 1 or rating > 5:
                        result["message"] = f"Invalid rating value: {rating}"
                        return result
                except (ValueError, TypeError):
                    result["message"] = f"Invalid rating format: {data.get('rating')}"
                    return result

                # Get or create patient
                patient = None
                if uhid:
                    patient = DoctorRatingAdapter.get_or_create_patient(
                        uhid=uhid,
                        patient_name=patient_name,
                        hospital=hospital,
                        phone=phone,
                        nationality=data.get("nationality", ""),
                        gender=data.get("gender", ""),
                    )

                # Determine patient type
                patient_type_raw = data.get("patient_type", "").upper()
                patient_type_map = {
                    "IP": PhysicianIndividualRating.PatientType.INPATIENT,
                    "OP": PhysicianIndividualRating.PatientType.OUTPATIENT,
                    "OPD": PhysicianIndividualRating.PatientType.OUTPATIENT,
                    "ER": PhysicianIndividualRating.PatientType.EMERGENCY,
                    "EMS": PhysicianIndividualRating.PatientType.EMERGENCY,
                    "DC": PhysicianIndividualRating.PatientType.DAYCASE,
                    "DAYCASE": PhysicianIndividualRating.PatientType.DAYCASE,
                }
                patient_type = patient_type_map.get(patient_type_raw, "")

                # Create individual rating
                individual_rating = PhysicianIndividualRating.objects.create(
                    staff=staff,
                    hospital=hospital,
                    source=source,
                    source_reference=source_reference,
                    doctor_name_raw=doctor_name_raw,
                    doctor_id=doctor_id,
                    doctor_name=doctor_name,
                    department_name=data.get("department", ""),
                    patient_uhid=uhid,
                    patient_name=patient_name,
                    patient_gender=data.get("gender", ""),
                    patient_age=DoctorRatingAdapter.parse_age(data.get("age", "")),
                    patient_nationality=data.get("nationality", ""),
                    patient_phone=phone,
                    patient_type=patient_type,
                    admit_date=admit_date,
                    discharge_date=discharge_date,
                    rating=rating,
                    feedback=data.get("feedback", ""),
                    rating_date=rating_date,
                    is_aggregated=False,
                    metadata={
                        "patient_type_raw": data.get("patient_type", ""),
                        "imported_at": timezone.now().isoformat(),
                    },
                )

                result["success"] = True
                result["rating_id"] = str(individual_rating.id)
                result["staff_matched"] = staff is not None
                result["staff_id"] = str(staff.id) if staff else None

        except Exception as e:
            logger.error(f"Error processing doctor rating: {str(e)}", exc_info=True)
            result["message"] = str(e)

        return result

    @staticmethod
    def process_bulk_ratings(records: List[Dict], hospital: Hospital, job: DoctorRatingImportJob) -> Dict:
        """
        Process multiple doctor rating records in bulk.

        Args:
            records: List of rating data dictionaries
            hospital: Hospital instance
            job: DoctorRatingImportJob instance for tracking

        Returns:
            Dict with summary statistics
        """
        results = {"total": len(records), "success": 0, "failed": 0, "skipped": 0, "staff_matched": 0, "errors": []}

        job.status = DoctorRatingImportJob.JobStatus.PROCESSING
        job.started_at = timezone.now()
        job.save()

        for idx, record in enumerate(records, 1):
            try:
                result = DoctorRatingAdapter.process_single_rating(data=record, hospital=hospital, source=job.source)

                if result["success"]:
                    results["success"] += 1
                    if result["staff_matched"]:
                        results["staff_matched"] += 1
                else:
                    results["failed"] += 1
                    results["errors"].append({"row": idx, "message": result["message"], "data": record})

                # Update progress every 10 records
                if idx % 10 == 0:
                    job.processed_count = idx
                    job.success_count = results["success"]
                    job.failed_count = results["failed"]
                    job.skipped_count = results["skipped"]
                    job.save()

            except Exception as e:
                results["failed"] += 1
                results["errors"].append({"row": idx, "message": str(e), "data": record})
                logger.error(f"Error processing record {idx}: {str(e)}", exc_info=True)

        # Final update
        job.processed_count = results["total"]
        job.success_count = results["success"]
        job.failed_count = results["failed"]
        job.skipped_count = results["skipped"]
        job.results = results
        job.completed_at = timezone.now()

        # Determine final status
        if results["failed"] == 0:
            job.status = DoctorRatingImportJob.JobStatus.COMPLETED
        elif results["success"] == 0:
            job.status = DoctorRatingImportJob.JobStatus.FAILED
        else:
            job.status = DoctorRatingImportJob.JobStatus.PARTIAL

        job.save()

        return results

    @staticmethod
    def aggregate_monthly_ratings(year: int, month: int, hospital: Hospital = None) -> Dict:
        """
        Aggregate individual ratings into monthly summaries.

        This should be called after importing ratings to update the monthly aggregates.

        Args:
            year: Year to aggregate
            month: Month to aggregate (1-12)
            hospital: Optional hospital filter (if None, aggregates all)

        Returns:
            Dict with summary of aggregations
        """
        from django.db.models import Avg, Count, Q

        results = {"aggregated": 0, "skipped_unlinked": 0, "errors": []}

        # Get unaggregated ratings for the period
        queryset = PhysicianIndividualRating.objects.filter(
            rating_date__year=year, rating_date__month=month, is_aggregated=False
        )

        if hospital:
            queryset = queryset.filter(hospital=hospital)

        # Count unlinked ratings before grouping
        unlinked_count = queryset.filter(staff__isnull=True).count()
        if unlinked_count:
            results["skipped_unlinked"] = unlinked_count
            logger.warning(
                f"Skipping {unlinked_count} unlinked individual ratings for {year}-{month:02d} (no staff match)"
            )

        # Group by staff
        staff_ratings = queryset.values("staff").annotate(
            avg_rating=Avg("rating"),
            total_count=Count("id"),
            positive_count=Count("id", filter=Q(rating__gte=4)),
            neutral_count=Count("id", filter=Q(rating__gte=3, rating__lt=4)),
            negative_count=Count("id", filter=Q(rating__lt=3)),
            rating_1_count=Count("id", filter=Q(rating=1)),
            rating_2_count=Count("id", filter=Q(rating=2)),
            rating_3_count=Count("id", filter=Q(rating=3)),
            rating_4_count=Count("id", filter=Q(rating=4)),
            rating_5_count=Count("id", filter=Q(rating=5)),
        )

        for group in staff_ratings:
            staff_id = group["staff"]
            if not staff_id:
                continue

            try:
                staff = Staff.objects.get(id=staff_id)

                # Update or create monthly rating
                monthly_rating, created = PhysicianMonthlyRating.objects.update_or_create(
                    staff=staff,
                    year=year,
                    month=month,
                    defaults={
                        "average_rating": round(group["avg_rating"], 2),
                        "total_surveys": group["total_count"],
                        "positive_count": group["positive_count"],
                        "neutral_count": group["neutral_count"],
                        "negative_count": group["negative_count"],
                        "rating_1_count": group["rating_1_count"],
                        "rating_2_count": group["rating_2_count"],
                        "rating_3_count": group["rating_3_count"],
                        "rating_4_count": group["rating_4_count"],
                        "rating_5_count": group["rating_5_count"],
                    },
                )

                # Mark individual ratings as aggregated
                queryset.filter(staff=staff).update(is_aggregated=True, aggregated_at=timezone.now())

                results["aggregated"] += 1

            except Exception as e:
                results["errors"].append({"staff_id": str(staff_id), "error": str(e)})

        return results
