"""
Integrations models - Inbound events from external systems

This module handles integration events from:
- HIS (Hospital Information System)
- Lab systems
- Radiology systems
- Pharmacy systems
- MOH (Ministry of Health)
- CHI (Council of Health Insurance)
- Other external systems
"""

import secrets
import hashlib

from django.conf import settings
from django.db import models
from django.utils import timezone

from apps.core.models import BaseChoices, TimeStampedModel, UUIDModel


class ExternalAPIKey(UUIDModel, TimeStampedModel):
    """
    API key for external integrations to access the external API.

    Each key is scoped to a hospital and can be restricted to specific entities.
    The plaintext key is only shown once at creation time.
    """

    name = models.CharField(
        max_length=200,
        help_text="Label for this integration (e.g. 'MOH Portal', 'CHI Connector')",
    )
    key_hash = models.CharField(
        max_length=128,
        unique=True,
        db_index=True,
        help_text="SHA-256 hash of the API key",
    )
    key_prefix = models.CharField(
        max_length=12,
        help_text="First 8 chars of the key for identification (e.g. 'hh_live_')",
    )

    # Scoping
    hospital = models.ForeignKey(
        "organizations.Hospital",
        on_delete=models.CASCADE,
        related_name="external_api_keys",
        null=True,
        blank=True,
        help_text="Scope this key to a hospital (null = all hospitals)",
    )

    # Access control
    is_active = models.BooleanField(default=True, db_index=True)
    allowed_entities = models.JSONField(
        default=list,
        blank=True,
        help_text='List of entities this key can access: ["complaints","inquiries","observations","appreciations","suggestions"]',
    )

    # Rate limiting
    rate_limit = models.IntegerField(
        default=60,
        help_text="Maximum requests per minute",
    )

    # Metadata
    description = models.TextField(blank=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="created_external_api_keys",
    )
    last_used_at = models.DateTimeField(null=True, blank=True)
    expires_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text="When this key expires (null = never)",
    )

    class Meta:
        ordering = ["-created_at"]
        verbose_name = "External API Key"
        verbose_name_plural = "External API Keys"

    def __str__(self):
        status = "Active" if self.is_active else "Inactive"
        hospital_name = self.hospital.name if self.hospital else "All"
        return f"{self.name} ({self.key_prefix}...) [{status}] - {hospital_name}"

    @classmethod
    def create_key(cls, name, hospital=None, allowed_entities=None, rate_limit=60, description="", created_by=None, expires_at=None):
        """
        Generate a new API key and return (instance, plaintext_key).
        The plaintext key is only available at creation time.
        """
        raw_key = secrets.token_hex(32)  # 64-char hex string
        key_prefix = raw_key[:8]
        key_hash = hashlib.sha256(raw_key.encode()).hexdigest()

        api_key = cls.objects.create(
            name=name,
            key_hash=key_hash,
            key_prefix=key_prefix,
            hospital=hospital,
            allowed_entities=allowed_entities or [],
            rate_limit=rate_limit,
            description=description,
            created_by=created_by,
            expires_at=expires_at,
        )

        return api_key, raw_key

    def verify(self, raw_key):
        """Verify a plaintext key against this stored hash."""
        if not self.is_active:
            return False
        if self.expires_at and timezone.now() > self.expires_at:
            return False
        computed_hash = hashlib.sha256(raw_key.encode()).hexdigest()
        return secrets.compare_digest(computed_hash, self.key_hash)

    def can_access(self, entity):
        """Check if this key can access the given entity type."""
        if not self.is_active:
            return False
        if entity == "lookup":
            return True
        if not self.allowed_entities:
            return True
        return entity in self.allowed_entities

    def record_usage(self):
        """Update last_used_at timestamp."""
        self.last_used_at = timezone.now()
        self.save(update_fields=["last_used_at"])


class EventStatus(BaseChoices):
    """Event processing status"""

    PENDING = "pending", "Pending"
    PROCESSING = "processing", "Processing"
    PROCESSED = "processed", "Processed"
    FAILED = "failed", "Failed"
    IGNORED = "ignored", "Ignored"


class SourceSystem(BaseChoices):
    """Source system choices"""

    HIS = "his", "Hospital Information System"
    LAB = "lab", "Laboratory System"
    RADIOLOGY = "radiology", "Radiology System"
    PHARMACY = "pharmacy", "Pharmacy System"
    MOH = "moh", "Ministry of Health"
    CHI = "chi", "Council of Health Insurance"
    PXCONNECT = "pxconnect", "PX Connect"
    OTHER = "other", "Other"


class InboundEvent(UUIDModel, TimeStampedModel):
    """
    Inbound integration event from external systems.

    Events trigger journey stage completions. For example:
    - Event code: "OPD_VISIT_COMPLETED" → completes "MD Consultation" stage
    - Event code: "LAB_ORDER_COMPLETED" → completes "Lab" stage
    - Event code: "RADIOLOGY_REPORT_FINALIZED" → completes "Radiology" stage
    - Event code: "PHARMACY_DISPENSED" → completes "Pharmacy" stage

    Processing flow:
    1. Event received via API (POST /api/integrations/events/)
    2. Stored with status=PENDING
    3. Celery task processes event:
       a. Find journey instance by encounter_id
       b. Find matching stage by trigger_event_code
       c. Complete the stage
       d. Create survey instance if configured
       e. Update event status to PROCESSED
    """

    # Source information
    source_system = models.CharField(
        max_length=50, choices=SourceSystem.choices, db_index=True, help_text="System that sent this event"
    )
    event_code = models.CharField(
        max_length=100, db_index=True, help_text="Event type code (e.g., OPD_VISIT_COMPLETED, LAB_ORDER_COMPLETED)"
    )

    # Identifiers
    encounter_id = models.CharField(max_length=100, db_index=True, help_text="Encounter ID from HIS system")
    patient_identifier = models.CharField(
        max_length=100, blank=True, db_index=True, help_text="Patient MRN or other identifier"
    )

    # Event data
    payload_json = models.JSONField(help_text="Full event payload from source system")

    # Processing status
    status = models.CharField(max_length=20, choices=EventStatus.choices, default=EventStatus.PENDING, db_index=True)

    # Timestamps
    received_at = models.DateTimeField(auto_now_add=True, db_index=True)
    processed_at = models.DateTimeField(null=True, blank=True)

    # Processing results
    error = models.TextField(blank=True, help_text="Error message if processing failed")
    processing_attempts = models.IntegerField(default=0, help_text="Number of processing attempts")

    # Extracted context (from payload)
    physician_license = models.CharField(max_length=100, blank=True, help_text="Physician license number from event")
    department_code = models.CharField(max_length=50, blank=True, help_text="Department code from event")

    # Metadata
    metadata = models.JSONField(default=dict, blank=True, help_text="Additional processing metadata")

    class Meta:
        ordering = ["-received_at"]
        indexes = [
            models.Index(fields=["status", "-received_at"]),
            models.Index(fields=["encounter_id", "event_code"]),
            models.Index(fields=["source_system", "-received_at"]),
        ]

    def __str__(self):
        return f"{self.source_system} - {self.event_code} - {self.encounter_id} ({self.status})"

    def mark_processing(self):
        """Mark event as being processed"""
        self.status = EventStatus.PROCESSING
        self.processing_attempts += 1
        self.save(update_fields=["status", "processing_attempts"])

    def mark_processed(self):
        """Mark event as successfully processed"""
        from django.utils import timezone

        self.status = EventStatus.PROCESSED
        self.processed_at = timezone.now()
        self.save(update_fields=["status", "processed_at"])

    def mark_failed(self, error_message):
        """Mark event as failed with error message"""
        self.status = EventStatus.FAILED
        self.error = error_message
        self.save(update_fields=["status", "error"])

    def mark_ignored(self, reason):
        """Mark event as ignored (e.g., no matching journey)"""
        self.status = EventStatus.IGNORED
        self.error = reason
        self.save(update_fields=["status", "error"])


class IntegrationConfig(UUIDModel, TimeStampedModel):
    """
    Configuration for external system integrations.

    Stores API endpoints, credentials, and mapping rules.
    """

    name = models.CharField(max_length=200, unique=True)
    source_system = models.CharField(max_length=50, choices=SourceSystem.choices, unique=True)

    # Connection details
    api_url = models.URLField(blank=True, help_text="API endpoint URL")
    api_key = models.CharField(max_length=500, blank=True, help_text="API key (encrypted)")

    # Configuration
    is_active = models.BooleanField(default=True)
    config_json = models.JSONField(
        default=dict, blank=True, help_text="Additional configuration (event mappings, field mappings, etc.)"
    )

    # Metadata
    description = models.TextField(blank=True)
    last_sync_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ["name"]

    def __str__(self):
        return f"{self.name} ({self.source_system})"


class PatientType(BaseChoices):
    """HIS Patient Type codes"""

    INPATIENT = "IP", "Inpatient"
    OPD = "OP", "Outpatient"
    EMS = "ED", "Emergency"
    DAYCASE = "DAYCASE", "Day Case"
    APPOINTMENT = "APPOINTMENT", "Appointment"


class SurveyTemplateMapping(UUIDModel, TimeStampedModel):
    """
    Maps patient types to survey templates for automatic survey delivery.

    This replaces the search-based template selection with explicit mappings.
    Allows administrators to control which survey template is sent for each
    patient type and hospital.

    Example:
    - PatientType: "1" (Inpatient) → Inpatient Satisfaction Survey
    - PatientType: "2" (OPD) → Outpatient Satisfaction Survey
    - PatientType: "APPOINTMENT" → Appointment Satisfaction Survey
    """

    # Mapping key
    patient_type = models.CharField(
        max_length=20, choices=PatientType.choices, db_index=True, help_text="Patient type from HIS system"
    )

    # Target survey
    survey_template = models.ForeignKey(
        "surveys.SurveyTemplate",
        on_delete=models.CASCADE,
        related_name="patient_type_mappings",
        help_text="Survey template to send for this patient type",
    )

    # Hospital specificity (null = global mapping)
    hospital = models.ForeignKey(
        "organizations.Hospital",
        on_delete=models.CASCADE,
        related_name="survey_template_mappings",
        null=True,
        blank=True,
        help_text="Hospital for this mapping (null = applies to all hospitals)",
    )

    # Activation
    is_active = models.BooleanField(default=True, db_index=True, help_text="Whether this mapping is active")

    # Delay configuration
    send_delay_hours = models.IntegerField(default=1, help_text="Hours after discharge to send survey")

    class Meta:
        ordering = ["hospital", "patient_type"]
        indexes = [
            models.Index(fields=["patient_type", "hospital", "is_active"]),
        ]
        # Ensure only one active mapping per patient type per hospital
        constraints = [
            models.UniqueConstraint(
                fields=["patient_type", "hospital"],
                condition=models.Q(is_active=True),
                name="unique_active_mapping_per_type_hospital",
            )
        ]

    def __str__(self):
        hospital_name = self.hospital.name if self.hospital else "All Hospitals"
        status = "Active" if self.is_active else "Inactive"
        return f"{self.get_patient_type_display()} -> {self.survey_template.name} ({hospital_name}) [{status}]"

    @staticmethod
    def get_template_for_patient_type(patient_type: str, hospital):
        """
        Get the active survey template for a patient type and hospital.

        Search order:
        1. Hospital-specific active mapping
        2. Global active mapping (hospital is null)

        Args:
            patient_type: HIS PatientType code (e.g., "1", "2", "APPOINTMENT")
            hospital: Hospital instance

        Returns:
            SurveyTemplate or None if no active mapping found
        """
        # Try hospital-specific mapping first
        mapping = SurveyTemplateMapping.objects.filter(
            patient_type=patient_type, hospital=hospital, is_active=True
        ).first()

        if mapping:
            return mapping.survey_template

        # Fall back to global mapping
        mapping = SurveyTemplateMapping.objects.filter(
            patient_type=patient_type, hospital__isnull=True, is_active=True
        ).first()

        return mapping.survey_template if mapping else None


class HISPatientVisit(UUIDModel, TimeStampedModel):
    """
    Stores patient visit data fetched from HIS system.

    Decoupled from survey creation - patient and visit data are saved
    on every fetch regardless of whether the visit is complete.
    Survey is created and linked only when the visit is complete.
    """

    patient = models.ForeignKey(
        "organizations.Patient",
        on_delete=models.CASCADE,
        related_name="his_visits",
        null=True,
        blank=True,
    )
    hospital = models.ForeignKey(
        "organizations.Hospital",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="his_visits",
    )
    admission_id = models.CharField(max_length=100, db_index=True)
    reg_code = models.CharField(max_length=100, blank=True, db_index=True)
    patient_id_his = models.CharField(max_length=100, blank=True, db_index=True, help_text="PatientID from HIS")
    patient_type = models.CharField(max_length=10, help_text="ED, IP, OP from HIS")
    admit_date = models.DateTimeField(null=True, blank=True)
    discharge_date = models.DateTimeField(null=True, blank=True, help_text="From HIS DischargeDate (null for OP)")
    effective_discharge_date = models.DateTimeField(
        null=True, blank=True, help_text="For OP: last visit timestamp when deemed complete"
    )
    visit_data = models.JSONField(default=dict, blank=True, help_text="Full patient demographic dict from HIS")
    visit_timeline = models.JSONField(default=list, blank=True, help_text="Extracted visit events for this patient")

    primary_doctor = models.CharField(
        max_length=300, blank=True, help_text="From HIS PrimaryDoctor (raw text fallback)"
    )
    primary_doctor_fk = models.ForeignKey(
        "organizations.Staff",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="his_visits_as_doctor",
        help_text="Resolved Staff record from PrimaryDoctor ID prefix",
    )
    consultant_id = models.CharField(max_length=50, blank=True, help_text="From HIS ConsultantID (raw text fallback)")
    consultant_fk = models.ForeignKey(
        "organizations.Staff",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="his_visits_as_consultant",
        help_text="Resolved Staff record from ConsultantID",
    )
    company_name = models.CharField(max_length=300, blank=True, help_text="From HIS CompanyName (insurance sponsor)")
    grade_name = models.CharField(max_length=100, blank=True, help_text="From HIS GradeName (insurance grade)")
    insurance_company_name = models.CharField(max_length=300, blank=True, help_text="From HIS InsuranceCompanyName")
    bill_type = models.CharField(max_length=20, blank=True, help_text="From HIS BillType (CS/CR)")
    is_vip = models.BooleanField(default=False, db_index=True, help_text="From HIS IsVIP")
    nationality = models.CharField(max_length=100, blank=True, help_text="From HIS PatientNationality")

    is_visit_complete = models.BooleanField(default=False, db_index=True)
    survey_instance = models.ForeignKey(
        "surveys.SurveyInstance",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="his_visit",
    )
    last_his_fetch_at = models.DateTimeField(
        null=True, blank=True, help_text="Last time this visit was seen in a HIS fetch"
    )

    class Meta:
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["patient_type", "is_visit_complete"]),
            models.Index(fields=["patient_type", "last_his_fetch_at"]),
            models.Index(fields=["admission_id", "is_visit_complete"]),
            models.Index(fields=["hospital", "patient_type", "is_visit_complete"]),
        ]
        constraints = [models.UniqueConstraint(fields=["admission_id"], name="unique_his_visit_per_admission")]

    def __str__(self):
        name = self.patient.get_full_name() if self.patient else self.patient_id_his
        return f"{name} ({self.patient_type}) - {self.admission_id}"

    @property
    def doctor_display(self):
        if self.primary_doctor_fk:
            return str(self.primary_doctor_fk)
        return self.primary_doctor or "-"

    @property
    def consultant_display(self):
        if self.consultant_fk:
            return str(self.consultant_fk)
        return self.consultant_id or "-"


class HISVisitEvent(UUIDModel, TimeStampedModel):
    """
    Individual visit event from HIS timeline.

    Extracted from visit_timeline JSON for better querying and filtering.
    Each event represents a single touchpoint in the patient journey.
    """

    visit = models.ForeignKey(
        HISPatientVisit,
        on_delete=models.CASCADE,
        related_name="visit_events",
    )
    event_type = models.CharField(max_length=200, blank=True, help_text="Type from HIS (e.g., 'Registration')")
    bill_date = models.CharField(max_length=50, blank=True, help_text="Raw BillDate from HIS (DD-Mon-YYYY HH:MM)")
    parsed_date = models.DateTimeField(null=True, blank=True, db_index=True, help_text="Parsed bill_date")
    patient_type = models.CharField(max_length=10, blank=True, help_text="PatientType for this event")
    visit_category = models.CharField(max_length=10, blank=True, help_text="Visit category: ED, IP, OP")
    admission_id = models.CharField(max_length=100, blank=True, db_index=True)
    patient_id = models.CharField(max_length=100, blank=True, db_index=True, help_text="PatientID from HIS")
    reg_code = models.CharField(max_length=100, blank=True, db_index=True)
    ssn = models.CharField(max_length=50, blank=True, db_index=True)
    mobile_no = models.CharField(max_length=50, blank=True)

    class Meta:
        ordering = ["parsed_date"]
        indexes = [
            models.Index(fields=["visit", "parsed_date"]),
            models.Index(fields=["visit_category", "parsed_date"]),
        ]

    def __str__(self):
        return f"{self.event_type} ({self.visit_category}) - {self.admission_id}"


class HISEventType(UUIDModel, TimeStampedModel):
    """
    Unique event types extracted from HIS data.

    Auto-populated when HISPatientVisit events are synced.
    Used to populate the event type dropdown in question configuration.
    """

    event_type = models.CharField(
        max_length=200,
        unique=True,
        help_text="HIS event type name (e.g., 'Lab Bill', 'Triage')",
    )
    patient_types = models.JSONField(
        default=list,
        blank=True,
        help_text="Patient types that have this event: ['OP', 'IP', 'ED']",
    )
    event_count = models.IntegerField(
        default=0,
        help_text="Total number of times this event type has been seen",
    )
    last_seen_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["event_type"]
        verbose_name = "HIS Event Type"
        verbose_name_plural = "HIS Event Types"

    def __str__(self):
        types = ", ".join(self.patient_types) if self.patient_types else "N/A"
        return f"{self.event_type} ({types})"


class EventMapping(UUIDModel, TimeStampedModel):
    """
    Maps external event codes to internal trigger codes.

    Example:
    - External: "VISIT_COMPLETE" → Internal: "OPD_VISIT_COMPLETED"
    - External: "LAB_RESULT_READY" → Internal: "LAB_ORDER_COMPLETED"
    """

    integration_config = models.ForeignKey(IntegrationConfig, on_delete=models.CASCADE, related_name="event_mappings")

    external_event_code = models.CharField(max_length=100, help_text="Event code from external system")
    internal_event_code = models.CharField(max_length=100, help_text="Internal event code used in journey stages")

    # Field mappings
    field_mappings = models.JSONField(
        default=dict, blank=True, help_text="Maps external field names to internal field names"
    )

    is_active = models.BooleanField(default=True)

    class Meta:
        unique_together = [["integration_config", "external_event_code"]]
        ordering = ["integration_config", "external_event_code"]

    def __str__(self):
        return f"{self.external_event_code} → {self.internal_event_code}"


class HISTestPatient(TimeStampedModel):
    """Test patient data loaded from visit_data.json for testing HIS integration."""

    admission_id = models.CharField(max_length=100, db_index=True)
    patient_id = models.CharField(max_length=100, db_index=True)
    patient_type = models.CharField(max_length=10, db_index=True)
    reg_code = models.CharField(max_length=100, blank=True, db_index=True)
    ssn = models.CharField(max_length=50, blank=True, db_index=True)
    mobile_no = models.CharField(max_length=20, blank=True, db_index=True)
    admit_date = models.DateTimeField(db_index=True)
    discharge_date = models.DateTimeField(null=True, blank=True)
    patient_data = models.JSONField(default=dict)
    hospital_id = models.CharField(max_length=20, blank=True)
    hospital_name = models.CharField(max_length=200, blank=True)
    patient_name = models.CharField(max_length=300, blank=True)

    class Meta:
        ordering = ["admit_date"]
        indexes = [
            models.Index(fields=["patient_type", "admit_date"]),
            models.Index(fields=["ssn", "admit_date"]),
            models.Index(fields=["mobile_no", "admit_date"]),
        ]
        constraints = [models.UniqueConstraint(fields=["admission_id"], name="unique_test_patient_admission")]

    def __str__(self):
        return f"{self.patient_name} ({self.patient_type}) - {self.admission_id}"


class HISTestVisit(TimeStampedModel):
    """Test visit events loaded from visit_data.json for testing HIS integration."""

    admission_id = models.CharField(max_length=100, db_index=True)
    patient_id = models.CharField(max_length=100, db_index=True)
    visit_category = models.CharField(max_length=10, db_index=True)
    event_type = models.CharField(max_length=200, blank=True)
    bill_date = models.DateTimeField(null=True, blank=True, db_index=True)
    reg_code = models.CharField(max_length=100, blank=True)
    ssn = models.CharField(max_length=50, blank=True, db_index=True)
    mobile_no = models.CharField(max_length=20, blank=True, db_index=True)
    visit_data = models.JSONField(default=dict)

    class Meta:
        ordering = ["bill_date"]
        indexes = [
            models.Index(fields=["admission_id", "visit_category"]),
            models.Index(fields=["patient_id", "visit_category"]),
            models.Index(fields=["admission_id", "bill_date"]),
        ]

    def __str__(self):
        return f"{self.event_type} ({self.visit_category}) - {self.admission_id}"
