"""
Observations services - Business logic for observation management.

This module provides services for:
- Creating and managing observations
- Converting observations to PX Actions
- Sending notifications
- Status management with audit logging
"""

import logging
from typing import Optional

from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.db import transaction
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils import timezone

from apps.notifications.services import NotificationService
from apps.organizations.models import Department, Hospital

from .models import (
    Observation,
    ObservationAttachment,
    ObservationNote,
    ObservationStatus,
    ObservationStatusLog,
    VALID_OBSERVATION_TRANSITIONS,
)

logger = logging.getLogger(__name__)
User = get_user_model()


class ObservationService:
    """
    Service class for observation management.
    """

    @staticmethod
    @transaction.atomic
    def create_observation(
        description: str,
        severity: str = "medium",
        category=None,
        title: str = "",
        description_en: str = "",
        location_text: str = "",
        location_type: str = "",
        incident_datetime=None,
        reporter_staff_id: str = "",
        reporter_name: str = "",
        reporter_phone: str = "",
        reporter_email: str = "",
        client_ip: str = None,
        user_agent: str = "",
        attachments: list = None,
        hospital=None,
        assigned_department=None,
        section=None,
        area=None,
        source=None,
        source_legacy: str = "",
    ) -> Observation:
        """
        Create a new observation.

        Args:
            description: Detailed description of the observation
            severity: Severity level (low, medium, high, critical)
            category: ObservationCategory instance (optional)
            title: Short title (optional)
            location_text: Location description (optional)
            incident_datetime: When the incident occurred (optional, defaults to now)
            reporter_staff_id: Staff ID of reporter (optional)
            reporter_name: Name of reporter (optional)
            reporter_phone: Phone of reporter (optional)
            reporter_email: Email of reporter (optional)
            client_ip: IP address of submitter (optional)
            user_agent: Browser user agent (optional)
            attachments: List of uploaded files (optional)

        Returns:
            Created Observation instance
        """
        observation = Observation.objects.create(
            description=description,
            description_en=description_en,
            severity=severity,
            category=category,
            title=title,
            location_text=location_text,
            location_type=location_type if location_type else "",
            incident_datetime=incident_datetime or timezone.now(),
            reporter_staff_id=reporter_staff_id,
            reporter_name=reporter_name,
            reporter_phone=reporter_phone,
            reporter_email=reporter_email,
            client_ip=client_ip,
            user_agent=user_agent,
            hospital=hospital,
            assigned_department=assigned_department,
            section=section,
            area=area,
            px_source=source,
            source=source_legacy,
        )

        # Create initial status log
        ObservationStatusLog.objects.create(
            observation=observation, from_status="", to_status=ObservationStatus.OPEN, comment="Observation submitted"
        )

        # Handle attachments
        if attachments:
            for file in attachments:
                ObservationAttachment.objects.create(
                    observation=observation,
                    file=file,
                )

        # Send notification to PX360 triage team (background)
        try:
            from apps.complaints.tasks import notify_staff_new_item

            notify_staff_new_item.delay("observation", str(observation.id))
        except Exception:
            logger.warning("Failed to queue notification task for observation", exc_info=True)

        # Trigger AI analysis in background
        try:
            from apps.observations.tasks import analyze_observation_with_ai

            analyze_observation_with_ai.delay(str(observation.id))
        except Exception:
            logger.warning("Failed to queue AI analysis task for observation", exc_info=True)

        logger.info(f"Created observation {observation.tracking_code}")
        return observation

    @staticmethod
    @transaction.atomic
    def change_status(
        observation: Observation,
        new_status: str,
        changed_by: User = None,
        comment: str = "",
    ) -> ObservationStatusLog:
        """
        Change observation status with audit logging.

        Args:
            observation: Observation instance
            new_status: New status value
            changed_by: User making the change (optional)
            comment: Comment about the change (optional)

        Returns:
            Created ObservationStatusLog instance
        """
        old_status = observation.status

        if old_status != new_status:
            allowed = VALID_OBSERVATION_TRANSITIONS.get(old_status, set())
            if new_status not in allowed:
                raise ValueError(
                    f"Invalid status transition from '{old_status}' to '{new_status}'. "
                    f"Allowed: {', '.join(sorted(allowed))}"
                )

        observation.status = new_status

        # Handle status-specific updates
        if new_status == ObservationStatus.IN_PROGRESS:
            if not observation.activated_at:
                observation.activated_at = timezone.now()
            if not observation.due_at:
                sla_config = observation.get_sla_config()
                if sla_config:
                    from datetime import timedelta

                    observation.due_at = observation.activated_at + timedelta(hours=sla_config.sla_hours)
        elif new_status == ObservationStatus.RESOLVED:
            observation.resolved_at = timezone.now()
            observation.resolved_by = changed_by
        elif new_status == ObservationStatus.CLOSED:
            observation.closed_at = timezone.now()
            observation.closed_by = changed_by

        observation.save()

        # Create status log
        status_log = ObservationStatusLog.objects.create(
            observation=observation,
            from_status=old_status,
            to_status=new_status,
            changed_by=changed_by,
            comment=comment,
        )

        # Send notifications based on status change
        if new_status == ObservationStatus.RESOLVED or new_status == ObservationStatus.CLOSED:
            ObservationService.notify_resolution(observation)

        logger.info(
            f"Observation {observation.tracking_code} status changed: {old_status} -> {new_status} by {changed_by}"
        )

        return status_log

    @staticmethod
    @transaction.atomic
    def triage_observation(
        observation: Observation,
        triaged_by: User,
        assigned_department: Department = None,
        assigned_to: User = None,
        new_status: str = None,
        note: str = "",
    ) -> Observation:
        """
        Triage an observation - assign department/owner and update status.

        Args:
            observation: Observation instance
            triaged_by: User performing the triage
            assigned_department: Department to assign (optional)
            assigned_to: User to assign (optional)
            new_status: New status (optional, defaults to TRIAGED or ASSIGNED)
            note: Triage note (optional)

        Returns:
            Updated Observation instance
        """
        # Update assignment
        if assigned_department:
            observation.assigned_department = assigned_department
        if assigned_to:
            observation.assigned_to = assigned_to

        # Determine new status
        if not new_status:
            new_status = ObservationStatus.IN_PROGRESS

        observation.save()

        # Change status
        ObservationService.change_status(
            observation=observation,
            new_status=new_status,
            changed_by=triaged_by,
            comment=note or f"Triaged by {triaged_by.get_full_name()}",
        )

        # Add note if provided
        if note:
            ObservationNote.objects.create(
                observation=observation,
                note=note,
                created_by=triaged_by,
                is_internal=True,
            )

        return observation

    @staticmethod
    @transaction.atomic
    def convert_to_action(
        observation: Observation,
        created_by: User,
        title: str = None,
        description: str = None,
        category: str = "other",
        priority: str = None,
        assigned_department: Department = None,
        assigned_to: User = None,
    ):
        """
        Convert an observation to a PX Action.

        Args:
            observation: Observation instance
            created_by: User creating the action
            title: Action title (optional, defaults to observation title)
            description: Action description (optional)
            category: Action category
            priority: Action priority (optional, derived from severity)
            assigned_department: Department to assign (optional)
            assigned_to: User to assign (optional)

        Returns:
            Created PXAction instance
        """
        from apps.px_action_center.models import ActionSource, PXAction

        # Get hospital from department or use first hospital
        hospital = None
        if assigned_department:
            hospital = assigned_department.hospital
        elif observation.assigned_department:
            hospital = observation.assigned_department.hospital
        else:
            hospital = Hospital.objects.first()

        if not hospital:
            raise ValueError("No hospital found for creating action")

        # Prepare title and description
        if not title:
            title = f"Observation {observation.tracking_code}"
            if observation.title:
                title += f" - {observation.title}"
            elif observation.category:
                title += f" - {observation.category.name_en}"

        if not description:
            description = f"""
Observation Details:
- Tracking Code: {observation.tracking_code}
- Category: {observation.category.name_en if observation.category else "N/A"}
- Severity: {observation.get_severity_display()}
- Location: {observation.location_text or "N/A"}
- Incident Date: {observation.incident_datetime.strftime("%Y-%m-%d %H:%M")}
- Reporter: {observation.reporter_display}

Description:
{observation.description}

View observation: /observations/{observation.id}/
            """.strip()

        # Map severity to priority
        if not priority:
            severity_to_priority = {
                "low": "low",
                "medium": "medium",
                "high": "high",
                "critical": "critical",
            }
            priority = severity_to_priority.get(observation.severity, "medium")

        # Create PX Action
        action = PXAction.objects.create(
            source_type=ActionSource.MANUAL,
            content_type=ContentType.objects.get_for_model(Observation),
            object_id=observation.id,
            title=title,
            description=description,
            hospital=hospital,
            department=assigned_department or observation.assigned_department,
            category=category,
            priority=priority,
            severity=observation.severity,
            assigned_to=assigned_to or observation.assigned_to,
            metadata={
                "observation_tracking_code": observation.tracking_code,
                "observation_id": str(observation.id),
            },
        )

        # Update observation with action link
        observation.action_id = action.id
        observation.save(update_fields=["action_id"])

        # Add note to observation
        ObservationNote.objects.create(
            observation=observation,
            note=f"Converted to PX Action: {action.id}",
            created_by=created_by,
            is_internal=True,
        )

        logger.info(f"Observation {observation.tracking_code} converted to action {action.id}")

        return action

    @staticmethod
    def add_note(
        observation: Observation,
        note: str,
        created_by: User,
        is_internal: bool = True,
    ) -> ObservationNote:
        """
        Add a note to an observation.

        Args:
            observation: Observation instance
            note: Note text
            created_by: User creating the note
            is_internal: Whether the note is internal-only

        Returns:
            Created ObservationNote instance
        """
        return ObservationNote.objects.create(
            observation=observation,
            note=note,
            created_by=created_by,
            is_internal=is_internal,
        )

    @staticmethod
    def add_attachment(
        observation: Observation,
        file,
        description: str = "",
    ) -> ObservationAttachment:
        """
        Add an attachment to an observation.

        Args:
            observation: Observation instance
            file: Uploaded file
            description: File description (optional)

        Returns:
            Created ObservationAttachment instance
        """
        return ObservationAttachment.objects.create(
            observation=observation,
            file=file,
            description=description,
        )

    @staticmethod
    def notify_new_observation(observation: Observation):
        """
        Send notification for new observation to PX360 triage team.

        Args:
            observation: Observation instance
        """
        try:
            from django.contrib.sites.models import Site

            px_admins = User.objects.filter(is_active=True, groups__name="PX Admin")

            subject = f"New Observation: {observation.tracking_code}"

            plain_message = f"""A new observation has been submitted and requires review.

Tracking Code: {observation.tracking_code}
Category: {observation.category.name_en if observation.category else "N/A"}
Severity: {observation.get_severity_display()}
Location: {observation.location_text or "N/A"}
Reporter: {observation.reporter_display}

Description:
{observation.description[:500]}{"..." if len(observation.description) > 500 else ""}

Please review this observation and assign it to the appropriate team member.""".strip()

            try:
                site_url = Site.objects.get_current().domain
            except Exception:
                site_url = getattr(settings, "SITE_URL", "")

            observation_url = f"{site_url}/observations/{observation.id}/"

            current_time = timezone.now().strftime("%Y-%m-%d %H:%M")

            for admin in px_admins:
                if admin.email:
                    context = {
                        "admin_name": admin.get_full_name(),
                        "observation": observation,
                        "observation_url": observation_url,
                        "site_url": site_url,
                        "current_time": current_time,
                    }
                    html_message = render_to_string("emails/new_observation_notification.html", context)

                    NotificationService.send_email(
                        email=admin.email,
                        subject=subject,
                        message=plain_message,
                        html_message=html_message,
                        related_object=observation,
                        metadata={
                            "observation_id": str(observation.id),
                            "tracking_code": observation.tracking_code,
                            "notification_type": "new_observation",
                        },
                    )

            logger.info(f"Sent new observation notification for {observation.tracking_code}")

        except Exception as e:
            logger.error(f"Failed to send new observation notification: {e}")

    @staticmethod
    def notify_assignment(observation: Observation):
        """
        Send notification when observation is assigned.

        Args:
            observation: Observation instance
        """
        try:
            if not observation.assigned_to:
                return

            from django.contrib.sites.models import Site

            subject = f"Observation Assigned: {observation.tracking_code}"

            plain_message = f"""An observation has been assigned to you.

Tracking Code: {observation.tracking_code}
Category: {observation.category.name_en if observation.category else "N/A"}
Severity: {observation.get_severity_display()}
Location: {observation.location_text or "N/A"}

Description:
{observation.description[:500]}{"..." if len(observation.description) > 500 else ""}

Please review this observation and take appropriate action.""".strip()

            try:
                site_url = Site.objects.get_current().domain
            except Exception:
                site_url = getattr(settings, "SITE_URL", "")

            observation_url = f"{site_url}/observations/{observation.id}/"

            context = {
                "recipient_name": observation.assigned_to.get_full_name(),
                "observation": observation,
                "observation_url": observation_url,
                "site_url": site_url,
            }
            html_message = render_to_string("emails/observation_assigned.html", context)

            if observation.assigned_to.email:
                NotificationService.send_email(
                    email=observation.assigned_to.email,
                    subject=subject,
                    message=plain_message,
                    html_message=html_message,
                    related_object=observation,
                    metadata={
                        "observation_id": str(observation.id),
                        "tracking_code": observation.tracking_code,
                        "notification_type": "observation_assigned",
                    },
                )

            logger.info(
                f"Sent assignment notification for {observation.tracking_code} to {observation.assigned_to.email}"
            )

        except Exception as e:
            logger.error(f"Failed to send assignment notification: {e}")

    @staticmethod
    def notify_resolution(observation: Observation):
        """
        Send internal notification when observation is resolved/closed.

        Args:
            observation: Observation instance
        """
        try:
            from django.contrib.sites.models import Site

            recipients = []

            if observation.assigned_to and observation.assigned_to.email:
                recipients.append(observation.assigned_to)

            if observation.assigned_department and observation.assigned_department.manager:
                if observation.assigned_department.manager.email:
                    recipients.append(observation.assigned_department.manager)

            if not recipients:
                return

            status_display = observation.get_status_display()

            subject = f"Observation {status_display}: {observation.tracking_code}"

            plain_message = f"""An observation has been {status_display.lower()}.

Tracking Code: {observation.tracking_code}
Category: {observation.category.name_en if observation.category else "N/A"}
Status: {status_display}

Resolution Notes:
{observation.resolution_notes or "No resolution notes provided."}""".strip()

            try:
                site_url = Site.objects.get_current().domain
            except Exception:
                site_url = getattr(settings, "SITE_URL", "")

            observation_url = f"{site_url}/observations/{observation.id}/"

            status_ar_map = {"Resolved": "حل", "Closed": "إغلاق"}

            for recipient in set(recipients):
                context = {
                    "recipient_name": recipient.get_full_name(),
                    "observation": observation,
                    "observation_url": observation_url,
                    "site_url": site_url,
                    "status_display": status_display,
                    "status_display_ar": status_ar_map.get(status_display, status_display),
                }
                html_message = render_to_string("emails/observation_resolved.html", context)

                NotificationService.send_email(
                    email=recipient.email,
                    subject=subject,
                    message=plain_message,
                    html_message=html_message,
                    related_object=observation,
                    metadata={
                        "observation_id": str(observation.id),
                        "tracking_code": observation.tracking_code,
                        "notification_type": "observation_resolved",
                    },
                )

            logger.info(f"Sent resolution notification for {observation.tracking_code}")

        except Exception as e:
            logger.error(f"Failed to send resolution notification: {e}")

    @staticmethod
    def get_statistics(hospital=None, department=None, date_from=None, date_to=None):
        """
        Get observation statistics.

        Args:
            hospital: Filter by hospital (optional)
            department: Filter by department (optional)
            date_from: Start date (optional)
            date_to: End date (optional)

        Returns:
            Dictionary with statistics
        """
        from django.db.models import Count, Q

        queryset = Observation.objects.all()

        if hospital:
            queryset = queryset.filter(assigned_department__hospital=hospital)
        if department:
            queryset = queryset.filter(assigned_department=department)
        if date_from:
            queryset = queryset.filter(created_at__gte=date_from)
        if date_to:
            queryset = queryset.filter(created_at__lte=date_to)

        # Status counts
        status_counts = queryset.values("status").annotate(count=Count("id"))
        status_dict = {item["status"]: item["count"] for item in status_counts}

        # Severity counts
        severity_counts = queryset.values("severity").annotate(count=Count("id"))
        severity_dict = {item["severity"]: item["count"] for item in severity_counts}

        # Category counts
        category_counts = queryset.values("category__name_en").annotate(count=Count("id")).order_by("-count")[:10]

        return {
            "total": queryset.count(),
            "open": status_dict.get("open", 0),
            "in_progress": status_dict.get("in_progress", 0),
            "resolved": status_dict.get("resolved", 0),
            "closed": status_dict.get("closed", 0),
            "anonymous_count": queryset.filter(Q(reporter_staff_id="") & Q(reporter_name="")).count(),
            "severity": severity_dict,
            "top_categories": list(category_counts),
        }
