"""
Surveys models - Survey templates and instances

This module implements the survey system that:
- Defines bilingual survey templates with questions
- Creates survey instances linked to journey stages
- Generates secure token-based survey links
- Collects and scores survey responses
- Triggers actions based on negative feedback
"""

import secrets

from django.core.signing import Signer
from django.db import models
from django.urls import reverse

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


class SurveyStatus(BaseChoices):
    """Survey status choices with enhanced tracking"""

    PENDING = "pending", "Pending (Scheduled, Not Yet Sent)"
    SENT = "sent", "Sent (Not Opened)"
    VIEWED = "viewed", "Viewed (Opened, Not Started)"
    IN_PROGRESS = "in_progress", "In Progress (Started, Not Completed)"
    COMPLETED = "completed", "Completed"
    ABANDONED = "abandoned", "Abandoned (Started but Left)"
    EXPIRED = "expired", "Expired"
    CANCELLED = "cancelled", "Cancelled"
    FAILED = "failed", "Failed"


class QuestionType(BaseChoices):
    """Survey question type choices"""

    RATING = "rating", "Rating (1-5 stars)"
    NPS = "nps", "NPS (0-10)"
    YES_NO = "yes_no", "Yes/No"
    MULTIPLE_CHOICE = "multiple_choice", "Multiple Choice"
    TEXT = "text", "Text (Short Answer)"
    TEXTAREA = "textarea", "Text Area (Long Answer)"
    LIKERT = "likert", "Likert Scale (1-5)"


class SurveyTemplate(UUIDModel, TimeStampedModel):
    """
    Survey template defines questions for a survey.

    Supports:
    - Bilingual questions (AR/EN)
    - Multiple question types
    - Scoring configuration
    - Branch logic (conditional questions)

    NOTE: Supports both template-based and event-based surveys.
    Event-based surveys use SurveyTemplate questions filtered by HIS events.
    """

    name = models.CharField(max_length=200)
    name_ar = models.CharField(max_length=200, blank=True, verbose_name="Name (Arabic)")

    # Configuration
    hospital = models.ForeignKey("organizations.Hospital", on_delete=models.CASCADE, related_name="survey_templates")

    # Survey type
    survey_type = models.CharField(
        max_length=50,
        choices=[
            ("stage", "Journey Stage Survey"),
            ("complaint_resolution", "Complaint Resolution Satisfaction"),
            ("general", "General Feedback"),
            ("nps", "Net Promoter Score"),
        ],
        default="stage",
        db_index=True,
    )

    # Scoring configuration
    scoring_method = models.CharField(
        max_length=20,
        choices=[
            ("average", "Average Score"),
            ("weighted", "Weighted Average"),
            ("nps", "NPS Calculation"),
        ],
        default="average",
    )
    negative_threshold = models.DecimalField(
        max_digits=3, decimal_places=1, default=3.0, help_text="Scores below this trigger PX actions (out of 5)"
    )

    # Configuration
    is_active = models.BooleanField(default=True, db_index=True)

    # Instructions & Consent
    instructions_en = models.TextField(blank=True, verbose_name="Instructions (English)")
    instructions_ar = models.TextField(blank=True, verbose_name="Instructions (Arabic)")
    consent_text_en = models.TextField(blank=True, verbose_name="Consent Text (English)")
    consent_text_ar = models.TextField(blank=True, verbose_name="Consent Text (Arabic)")
    requires_consent = models.BooleanField(default=True, verbose_name="Require patient consent")

    class Meta:
        ordering = ["hospital", "name"]
        indexes = [
            models.Index(fields=["hospital", "survey_type", "is_active"]),
        ]

    def __str__(self):
        return self.name

    def get_question_count(self):
        return self.questions.count()


class SurveyQuestion(UUIDModel, TimeStampedModel):
    """
    Survey question belonging to a template.

    Questions can be:
    - Base questions (is_base=True): Always included in surveys from this template
    - Event-based questions (event_type set): Only included when the patient
      experienced that specific event type in their HIS visit
    - Conditional questions (is_conditional=True): Only shown when routing rules match
    """

    survey_template = models.ForeignKey(SurveyTemplate, on_delete=models.CASCADE, related_name="questions")

    text = models.TextField(verbose_name="Question Text (English)")
    text_ar = models.TextField(blank=True, verbose_name="Question Text (Arabic)")
    question_type = models.CharField(max_length=20, choices=QuestionType.choices, default="rating")
    order = models.IntegerField(default=0, help_text="Display order")
    is_required = models.BooleanField(default=True)
    choices_json = models.JSONField(blank=True, default=list, help_text="Array of choice objects")

    is_base = models.BooleanField(
        default=False,
        db_index=True,
        help_text="Always include this question regardless of events",
    )
    event_type = models.CharField(
        max_length=200,
        blank=True,
        db_index=True,
        help_text="HIS event type that triggers this question (e.g., 'Lab Bill', 'Triage')",
    )
    is_conditional = models.BooleanField(
        default=False,
        db_index=True,
        help_text="If True, this question is hidden by default and only shown via routing rules",
    )

    class Meta:
        ordering = ["survey_template", "order"]

    def __str__(self):
        text = self.text or ""
        return f"Q{self.order}: {text[:60]}"


class RoutingOperator(BaseChoices):
    EQUALS = "equals", "Equals"
    NOT_EQUALS = "not_equals", "Not Equals"
    CONTAINS = "contains", "Contains"
    GREATER_THAN = "gt", "Greater Than"
    LESS_THAN = "lt", "Less Than"
    IN_LIST = "in_list", "In List"
    ANSWERED = "answered", "Is Answered"
    NOT_ANSWERED = "not_answered", "Is Not Answered"


class RoutingAction(BaseChoices):
    SKIP_TO = "skip_to", "Skip To Question"
    END_SURVEY = "end_survey", "End Survey"


class QuestionRoutingRule(UUIDModel, TimeStampedModel):
    """
    Routing rule for dynamic survey flow.

    Defines conditional logic: when a user answers source_question,
    the system evaluates the operator against the answer value.
    If the condition matches, the action is triggered.

    Examples:
    - If Q1 (rating) answer < 3, skip to Q5 (complaint details)
    - If Q2 (yes_no) answer == 'yes', end survey early
    - If Q3 (multiple_choice) answer in ['a', 'b'], skip to Q7
    """

    survey_template = models.ForeignKey(SurveyTemplate, on_delete=models.CASCADE, related_name="routing_rules")
    source_question = models.ForeignKey(
        SurveyQuestion,
        on_delete=models.CASCADE,
        related_name="outgoing_routing_rules",
        help_text="The question whose answer triggers this rule",
    )
    operator = models.CharField(max_length=20, choices=RoutingOperator.choices)
    value = models.JSONField(
        blank=True,
        default=dict,
        help_text="Comparison value (e.g., 'yes', 3, ['a','b']). Not needed for answered/not_answered.",
    )
    action = models.CharField(max_length=20, choices=RoutingAction.choices)
    target_question = models.ForeignKey(
        SurveyQuestion,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name="incoming_routing_rules",
        help_text="Target question for skip_to action. Not needed for end_survey.",
    )
    order = models.IntegerField(
        default=0, help_text="Evaluation order when multiple rules exist for the same source question"
    )

    class Meta:
        ordering = ["source_question", "order"]
        indexes = [
            models.Index(fields=["survey_template", "source_question", "order"]),
        ]

    def __str__(self):
        return f"Rule: Q{self.source_question.order} {self.get_operator_display()} {self.value} -> {self.get_action_display()}"

    def clean(self):
        from django.core.exceptions import ValidationError

        if self.action == "skip_to" and not self.target_question:
            raise ValidationError({"target_question": "Target question is required for skip_to action."})
        if self.action == "end_survey" and self.target_question:
            raise ValidationError({"target_question": "Target question should not be set for end_survey action."})
        if self.operator not in ["answered", "not_answered"] and not self.value:
            raise ValidationError({"value": "Value is required for this operator."})


class SurveyInstance(UUIDModel, TimeStampedModel, TenantModel):
    """
    Survey instance - an actual survey sent to a patient or staff.

    Linked to:
    - Survey template (defines questions)
    - Patient (recipient - optional if staff)
    - Staff (recipient - optional if patient)
    - Journey stage (optional - if stage survey)
    - Encounter (optional)

    Tenant-aware: All surveys are scoped to a hospital.
    """

    survey_template = models.ForeignKey(SurveyTemplate, on_delete=models.PROTECT, related_name="instances")

    # Patient information
    patient = models.ForeignKey(
        "organizations.Patient", on_delete=models.CASCADE, related_name="surveys", null=True, blank=True
    )

    # Staff information
    staff = models.ForeignKey(
        "organizations.Staff",
        on_delete=models.CASCADE,
        related_name="surveys",
        null=True,
        blank=True,
        help_text="Staff recipient (if survey is for staff)",
    )

    # Journey linkage
    journey_instance = models.ForeignKey(
        "journeys.PatientJourneyInstance", on_delete=models.CASCADE, null=True, blank=True, related_name="surveys"
    )
    encounter_id = models.CharField(max_length=100, blank=True, db_index=True)

    # Delivery
    delivery_channel = models.CharField(
        max_length=20,
        choices=[
            ("sms", "SMS"),
            ("whatsapp", "WhatsApp"),
            ("email", "Email"),
        ],
        default="sms",
    )
    recipient_phone = models.CharField(max_length=20, blank=True, null=True)
    recipient_email = models.EmailField(blank=True, null=True)

    # Access token for secure link
    access_token = models.CharField(max_length=100, unique=True, blank=True, help_text="Secure token for survey access")
    token_expires_at = models.DateTimeField(null=True, blank=True, help_text="Token expiration date")

    # Status
    status = models.CharField(max_length=20, choices=SurveyStatus.choices, default=SurveyStatus.SENT, db_index=True)

    # Timestamps
    sent_at = models.DateTimeField(null=True, blank=True, db_index=True)
    scheduled_send_at = models.DateTimeField(
        null=True, blank=True, db_index=True, help_text="When this survey should be sent (for delayed sending)"
    )
    opened_at = models.DateTimeField(null=True, blank=True)
    completed_at = models.DateTimeField(null=True, blank=True)

    # Enhanced tracking
    open_count = models.IntegerField(default=0, help_text="Number of times survey link was opened")
    last_opened_at = models.DateTimeField(null=True, blank=True, help_text="Most recent time survey was opened")
    time_spent_seconds = models.IntegerField(null=True, blank=True, help_text="Total time spent on survey in seconds")

    # Scoring
    total_score = models.DecimalField(
        max_digits=5, decimal_places=2, null=True, blank=True, help_text="Calculated total score"
    )
    is_negative = models.BooleanField(default=False, db_index=True, help_text="True if score below threshold")

    # Metadata
    metadata = models.JSONField(default=dict, blank=True)

    # Patient contact tracking (for negative surveys)
    patient_contacted = models.BooleanField(
        default=False, help_text="Whether patient was contacted about negative survey"
    )
    patient_contacted_at = models.DateTimeField(null=True, blank=True)
    patient_contacted_by = models.ForeignKey(
        "accounts.User",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="contacted_surveys",
        help_text="User who contacted the patient",
    )
    contact_notes = models.TextField(blank=True, help_text="Notes from patient contact")
    issue_resolved = models.BooleanField(default=False, help_text="Whether the issue was resolved/explained")

    # Patient comment and AI analysis
    comment = models.TextField(blank=True, help_text="Optional comment from patient")
    comment_analyzed = models.BooleanField(default=False, help_text="Whether the comment has been analyzed by AI")
    comment_analysis = models.JSONField(default=dict, blank=True, help_text="AI analysis results for the comment")

    # Language preference
    completed_language = models.CharField(
        max_length=10, blank=True, help_text="Language selected by patient when completing survey (en/ar)"
    )

    class Meta:
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["patient", "-created_at"]),
            models.Index(fields=["staff", "-created_at"]),
            models.Index(fields=["status", "-sent_at"]),
            models.Index(fields=["is_negative", "-completed_at"]),
            models.Index(fields=["survey_template", "status", "-created_at"]),
        ]

    def __str__(self):
        return f"{self.survey_template.name} - {self.get_recipient_name()}"

    def clean(self):
        """Validate that exactly one of patient or staff is set"""
        from django.core.exceptions import ValidationError

        if self.patient and self.staff:
            raise ValidationError("Cannot specify both patient and staff for a survey")
        if not self.patient and not self.staff:
            raise ValidationError("Must specify either a patient or staff recipient")

    def get_recipient(self):
        """Get the recipient object (patient or staff)"""
        return self.patient if self.patient else self.staff

    def get_recipient_name(self):
        """Get recipient name (patient or staff)"""
        if self.patient:
            return self.patient.get_full_name()
        elif self.staff:
            return self.staff.get_full_name()
        elif self.metadata and self.metadata.get("recipient_name"):
            return self.metadata.get("recipient_name")
        return "Unknown"

    def get_recipient_email(self):
        """Get recipient email (patient or staff)"""
        if self.patient:
            return self.patient.email
        elif self.staff:
            return self.staff.email
        return None

    def get_recipient_phone(self):
        """Get recipient phone (patient or staff)"""
        if self.patient:
            return self.patient.phone
        elif self.staff:
            return self.staff.phone
        return None

    def save(self, *args, **kwargs):
        """Generate access token on creation"""
        if not self.access_token:
            self.access_token = secrets.token_urlsafe(32)

        # Set token expiration
        if not self.token_expires_at:
            from datetime import timedelta
            from django.conf import settings
            from django.utils import timezone

            days = getattr(settings, "SURVEY_TOKEN_EXPIRY_DAYS", 30)
            self.token_expires_at = timezone.now() + timedelta(days=days)

        super().save(*args, **kwargs)

    def get_survey_url(self):
        """Generate secure survey URL"""
        return f"/surveys/s/{self.access_token}/"

    def calculate_score(self):
        """
        Calculate total score from responses.
        """
        responses = self.responses.all()
        if not responses.exists():
            return None

        scoring_method = self.survey_template.scoring_method

        if scoring_method in ("average", "weighted"):
            rating_responses = responses.filter(question__question_type__in=["rating", "likert", "nps"])
            if rating_responses.exists():
                total = sum(float(r.numeric_value or 0) for r in rating_responses)
                count = rating_responses.count()
                score = total / count if count > 0 else 0
            else:
                score = 0

        else:  # NPS
            nps_responses = responses.filter(question__question_type="nps")
            if nps_responses.exists():
                promoters = nps_responses.filter(numeric_value__gte=9).count()
                detractors = nps_responses.filter(numeric_value__lte=6).count()
                total = nps_responses.count()
                score = ((promoters - detractors) / total * 100) if total > 0 else 0
            else:
                score = 0

        self.total_score = score
        self.is_negative = score < float(self.survey_template.negative_threshold)
        self.save(update_fields=["total_score", "is_negative"])

        return score


class SurveyResponse(UUIDModel, TimeStampedModel):
    """
    Survey response - answer to a specific question.
    """

    survey_instance = models.ForeignKey(SurveyInstance, on_delete=models.CASCADE, related_name="responses")
    question = models.ForeignKey(SurveyQuestion, on_delete=models.PROTECT, related_name="responses")

    # Response value (type depends on question type)
    numeric_value = models.DecimalField(
        max_digits=10, decimal_places=2, null=True, blank=True, help_text="For rating, NPS, Likert questions"
    )
    text_value = models.TextField(blank=True, help_text="For text, textarea questions")
    choice_value = models.CharField(max_length=200, blank=True, help_text="For multiple choice questions")

    response_time_seconds = models.FloatField(
        null=True, blank=True, help_text="Time taken to answer this question in seconds"
    )

    # AI analysis for text responses
    analysis = models.JSONField(default=dict, blank=True, help_text="AI analysis results for text responses")

    class Meta:
        ordering = ["survey_instance", "question__order"]
        unique_together = [["survey_instance", "question"]]


class SurveyTracking(UUIDModel, TimeStampedModel):
    """
    Detailed survey engagement tracking.

    Tracks multiple interactions with survey:
    - Page views
    - Time spent on survey
    - Abandonment events
    - Device/browser information
    """

    survey_instance = models.ForeignKey(SurveyInstance, on_delete=models.CASCADE, related_name="tracking_events")

    # Event type
    event_type = models.CharField(
        max_length=50,
        choices=[
            ("page_view", "Page View"),
            ("survey_started", "Survey Started"),
            ("question_answered", "Question Answered"),
            ("survey_abandoned", "Survey Abandoned"),
            ("survey_completed", "Survey Completed"),
            ("reminder_sent", "Reminder Sent"),
        ],
        db_index=True,
    )

    # Timing
    time_on_page = models.IntegerField(null=True, blank=True, help_text="Time spent on page in seconds")
    total_time_spent = models.IntegerField(
        null=True, blank=True, help_text="Total time spent on survey so far in seconds"
    )

    # Context
    current_question = models.IntegerField(null=True, blank=True, help_text="Question number when event occurred")

    # Device info
    user_agent = models.TextField(blank=True)
    ip_address = models.GenericIPAddressField(null=True, blank=True)
    device_type = models.CharField(max_length=50, blank=True, help_text="mobile, tablet, desktop")
    browser = models.CharField(max_length=100, blank=True)

    # Location (optional, for analytics)
    country = models.CharField(max_length=100, blank=True)
    city = models.CharField(max_length=100, blank=True)

    # Metadata
    metadata = models.JSONField(default=dict, blank=True)

    class Meta:
        ordering = ["survey_instance", "created_at"]
        indexes = [
            models.Index(fields=["survey_instance", "event_type", "-created_at"]),
            models.Index(fields=["event_type", "-created_at"]),
        ]

    def __str__(self):
        return f"{self.survey_instance.id} - {self.event_type} at {self.created_at}"

    @classmethod
    def track_event(cls, survey_instance, event_type, **kwargs):
        """
        Helper method to track a survey event.

        Args:
            survey_instance: SurveyInstance
            event_type: str - event type key
            **kwargs: additional fields (time_on_page, current_question, etc.)

        Returns:
            SurveyTracking instance
        """
        return cls.objects.create(survey_instance=survey_instance, event_type=event_type, **kwargs)


class BulkSurveyJob(UUIDModel, TimeStampedModel):
    """
    Tracks bulk survey sending jobs for background processing.

    Used for HIS import and other bulk survey operations.
    """

    class JobStatus(models.TextChoices):
        PENDING = "pending", "Pending"
        PROCESSING = "processing", "Processing"
        COMPLETED = "completed", "Completed"
        FAILED = "failed", "Failed"
        PARTIAL = "partial", "Partially Completed"

    class JobSource(models.TextChoices):
        HIS_IMPORT = "his_import", "HIS Import"
        CSV_UPLOAD = "csv_upload", "CSV Upload"
        MANUAL = "manual", "Manual"

    # Job info
    name = models.CharField(max_length=200, blank=True)
    status = models.CharField(max_length=20, choices=JobStatus.choices, default=JobStatus.PENDING, db_index=True)
    source = models.CharField(max_length=20, choices=JobSource.choices, default=JobSource.MANUAL)

    # User who initiated
    created_by = models.ForeignKey(
        "accounts.User", on_delete=models.SET_NULL, null=True, related_name="bulk_survey_jobs"
    )

    # Hospital
    hospital = models.ForeignKey("organizations.Hospital", on_delete=models.CASCADE, related_name="bulk_survey_jobs")

    # Survey template used
    survey_template = models.ForeignKey(SurveyTemplate, on_delete=models.SET_NULL, null=True, related_name="bulk_jobs")

    # Progress tracking
    total_patients = models.IntegerField(default=0)
    processed_count = models.IntegerField(default=0)
    success_count = models.IntegerField(default=0)
    failed_count = models.IntegerField(default=0)

    # Delivery settings
    delivery_channel = models.CharField(max_length=20, default="sms")
    custom_message = models.TextField(blank=True)

    # Patient data (stored as JSON list)
    patient_data = models.JSONField(default=list, help_text="List of patient IDs and file numbers to process")

    # Results
    results = models.JSONField(default=dict, blank=True, help_text="Detailed results including successes and failures")

    # Error info
    error_message = models.TextField(blank=True)

    # Timestamps
    started_at = models.DateTimeField(null=True, blank=True)
    completed_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["status", "-created_at"]),
            models.Index(fields=["created_by", "-created_at"]),
            models.Index(fields=["hospital", "-created_at"]),
        ]

    def __str__(self):
        return f"Bulk Survey Job {self.id[:8]} - {self.status}"

    @property
    def progress_percentage(self):
        """Calculate progress percentage"""
        if self.total_patients == 0:
            return 0
        return int((self.processed_count / self.total_patients) * 100)

    @property
    def is_complete(self):
        """Check if job is complete"""
        return self.status in [self.JobStatus.COMPLETED, self.JobStatus.FAILED, self.JobStatus.PARTIAL]
