"""
Service layer for orchestrating multi-channel outbound notifications and audit trails.
"""

import math
from datetime import UTC, datetime
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.core.exceptions import NotFoundException, ValidationException
from app.core.pagination import PaginationParams
from app.core.responses import PaginatedResponse, PaginationMeta
from app.models.appointment import Appointment
from app.models.notification import (
    Notification,
    NotificationChannel,
    NotificationStatus,
)
from app.models.patient import Patient
from app.models.report import Report, ReportStatus
from app.models.user import User
from app.repositories.notification_repository import NotificationRepository
from app.schemas.notification import NotificationResponse, NotificationSendRequest
from app.services.client_link_service import ClientLinkService
from app.services.email_service import EmailService, normalize_email_address


class NotificationService:
    """Business logic for dispatching patient notifications and maintaining audit logs."""

    def __init__(
        self,
        notification_repo: NotificationRepository | None = None,
        client_link_service: ClientLinkService | None = None,
        email_service: EmailService | None = None,
    ) -> None:
        self.repo = notification_repo or NotificationRepository()
        self.link_service = client_link_service or ClientLinkService()
        self.email_service = email_service or EmailService()

    def _to_response(self, notif: Notification) -> NotificationResponse:
        return NotificationResponse(
            id=notif.id,
            patient_id=notif.patient_id,
            order_id=notif.order_id,
            report_id=notif.report_id,
            channel=notif.channel,
            status=notif.status,
            recipient=notif.recipient,
            subject=notif.subject,
            message=notif.message,
            sent_at=notif.sent_at,
            error_message=notif.error_message,
            created_at=notif.created_at,
            updated_at=notif.updated_at,
        )

    async def send_report_released_notification(
        self,
        db: AsyncSession,
        report: Report,
    ) -> Notification:
        """
        Generate 72h secure report link, persist audit notification, and dispatch email.
        """
        order = report.order
        if not order or not order.patient:
            raise ValidationException(message=f"Report {report.id} does not have an associated patient.")

        patient: Patient = order.patient
        lab = order.lab

        # Generate 72-hour access link in PostgreSQL
        token, view_url, _ = await self.link_service.create_report_token(
            report_id=report.id,
            patient_id=patient.id,
            lab_id=order.lab_id,
        )

        subject = f"Your Diagnostic Laboratory Report is Ready - {order.order_number}"
        recipient = normalize_email_address(patient.email)

        # Guard: never route a phone number through the email transport.
        if not recipient:
            reason = (
                "Patient has no registered email address for electronic dispatch."
                if not (patient.email or "").strip()
                else "Patient email address is invalid; no email was dispatched."
            )
            notif = Notification(
                patient_id=patient.id,
                order_id=order.id,
                report_id=report.id,
                channel=NotificationChannel.EMAIL,
                status=NotificationStatus.FAILED,
                recipient="",
                subject=subject,
                message=f"Report ready notification generated with link: {view_url}",
                error_message=reason,
            )
            created = await self.repo.create(db, notif)
            await db.commit()
            return created

        # Render HTML email
        html_content = self.email_service.render_template(
            "report_ready.html",
            {
                "lab": lab,
                "patient": patient,
                "order": order,
                "report": report,
                "report_view_url": view_url,
            },
        )

        notif = Notification(
            patient_id=patient.id,
            order_id=order.id,
            report_id=report.id,
            channel=NotificationChannel.EMAIL,
            status=NotificationStatus.PENDING,
            recipient=recipient,
            subject=subject,
            message=f"Report ready notification dispatched with secure link: {view_url}",
        )
        notif = await self.repo.create(db, notif)

        # Dispatch email safely
        success, error_msg = await self.email_service.send_email(
            recipient=recipient,
            subject=subject,
            html_content=html_content,
        )

        if success:
            notif = await self.repo.update_status(
                db,
                notif,
                status=NotificationStatus.SENT,
                sent_at=datetime.now(UTC),
            )
        else:
            notif = await self.repo.update_status(
                db,
                notif,
                status=NotificationStatus.FAILED,
                error_message=error_msg,
            )

        await db.commit()
        return notif

    async def resend_report_email(
        self,
        db: AsyncSession,
        report: Report,
        email: str,
        current_user: User,
    ) -> Notification:
        """Re-dispatch a RELEASED report to a (possibly corrected) email address.

        Regenerates a fresh 72h secure link, audits a new notification row, and
        persists the address onto the patient record so future releases deliver.
        """
        order = report.order
        if not order or not order.patient:
            raise ValidationException(message=f"Report {report.id} does not have an associated patient.")
        if not current_user.is_superadmin and order.lab_id != current_user.lab_id:
            raise NotFoundException(message=f"Report {report.id} not found in this branch")
        if report.status != ReportStatus.RELEASED:
            raise ValidationException(
                message="Only released reports can be re-dispatched. "
                f"Current status is '{report.status.value}'."
            )

        patient: Patient = order.patient
        lab = order.lab
        address = normalize_email_address(email)
        if not address:
            raise ValidationException(message="Enter a valid email address before resending the report.")

        token, view_url, _ = await self.link_service.create_report_token(
            report_id=report.id,
            patient_id=patient.id,
            lab_id=order.lab_id,
        )
        subject = f"Your Diagnostic Laboratory Report is Ready - {order.order_number}"
        html_content = self.email_service.render_template(
            "report_ready.html",
            {
                "lab": lab,
                "patient": patient,
                "order": order,
                "report": report,
                "report_view_url": view_url,
            },
        )
        notif = Notification(
            patient_id=patient.id,
            order_id=order.id,
            report_id=report.id,
            channel=NotificationChannel.EMAIL,
            status=NotificationStatus.PENDING,
            recipient=address,
            subject=subject,
            message=f"Report re-dispatched by {current_user.name} with secure link: {view_url}",
        )
        notif = await self.repo.create(db, notif)

        success, error_msg = await self.email_service.send_email(
            recipient=address,
            subject=subject,
            html_content=html_content,
        )
        if success:
            notif = await self.repo.update_status(
                db, notif, status=NotificationStatus.SENT, sent_at=datetime.now(UTC)
            )
        else:
            notif = await self.repo.update_status(
                db, notif, status=NotificationStatus.FAILED, error_message=error_msg
            )

        if patient.email != address:
            patient.email = address
        await db.commit()
        return notif

    async def send_appointment_confirmed_notification(
        self,
        db: AsyncSession,
        appointment: Appointment,
    ) -> Notification:
        """
        Generate 72h appointment view link, persist audit record, and dispatch email.
        """
        patient: Patient | None = appointment.patient
        if not patient:
            raise ValidationException(
                message=f"Appointment {appointment.id} does not have an associated patient."
            )

        lab = appointment.lab
        token, view_url, _ = await self.link_service.create_appointment_token(
            appointment_id=appointment.id,
            patient_id=patient.id,
            lab_id=appointment.lab_id,
        )

        import urllib.parse
        from datetime import timedelta

        booking_reference = f"APT-{appointment.id.hex[:8].upper()}"
        subject = f"Your Laboratory Appointment is Confirmed - {booking_reference}"
        recipient = normalize_email_address(patient.email)

        start_dt = appointment.slot.start_at if appointment.slot else appointment.created_at
        end_dt = appointment.slot.end_at if appointment.slot else (start_dt + timedelta(minutes=30))
        dates_param = f"{start_dt.strftime('%Y%m%dT%H%M%SZ')}/{end_dt.strftime('%Y%m%dT%H%M%SZ')}"
        lab_name = lab.name if lab else "Munal Medical Laboratory"
        lab_address = lab.address if lab else "Kathmandu, Nepal"
        cal_details = (
            f"Laboratory Appointment\n"
            f"Booking Reference: {booking_reference}\n"
            f"Patient: {patient.name}\n"
            f"Location: {lab_name} ({lab_address})\n\n"
            f"Please arrive 10 minutes prior to your allocated time window.\n"
            f"View details: {view_url}"
        )
        cal_query = urllib.parse.urlencode(
            {
                "action": "TEMPLATE",
                "text": f"Lab Appointment ({booking_reference}) - {lab_name}",
                "dates": dates_param,
                "details": cal_details,
                "location": f"{lab_name}, {lab_address}",
            }
        )
        google_calendar_url = f"https://calendar.google.com/calendar/render?{cal_query}"

        if not recipient:
            reason = (
                "Patient has no registered email address for electronic dispatch."
                if not (patient.email or "").strip()
                else "Patient email address is invalid; no email was dispatched."
            )
            notif = Notification(
                patient_id=patient.id,
                order_id=appointment.visit_order_id,
                report_id=None,
                channel=NotificationChannel.EMAIL,
                status=NotificationStatus.FAILED,
                recipient="",
                subject=subject,
                message=f"Appointment confirmation generated with link: {view_url}",
                error_message=reason,
            )
            created = await self.repo.create(db, notif)
            await db.commit()
            return created

        html_content = self.email_service.render_template(
            "appointment_confirmed.html",
            {
                "lab": lab,
                "patient": patient,
                "appointment": appointment,
                "appointment_view_url": view_url,
                "booking_reference": booking_reference,
                "google_calendar_url": google_calendar_url,
            },
        )

        notif = Notification(
            patient_id=patient.id,
            order_id=appointment.visit_order_id,
            report_id=None,
            channel=NotificationChannel.EMAIL,
            status=NotificationStatus.PENDING,
            recipient=recipient,
            subject=subject,
            message=f"Appointment confirmation dispatched with secure link: {view_url}",
        )
        notif = await self.repo.create(db, notif)

        success, error_msg = await self.email_service.send_email(
            recipient=recipient,
            subject=subject,
            html_content=html_content,
        )

        if success:
            notif = await self.repo.update_status(
                db,
                notif,
                status=NotificationStatus.SENT,
                sent_at=datetime.now(UTC),
            )
        else:
            notif = await self.repo.update_status(
                db,
                notif,
                status=NotificationStatus.FAILED,
                error_message=error_msg,
            )

        await db.commit()
        return notif

    async def send_custom_notification(
        self,
        db: AsyncSession,
        data: NotificationSendRequest,
        current_user: User,
    ) -> NotificationResponse:
        """Staff-initiated manual notification dispatch."""
        from app.repositories.patient_repository import PatientRepository

        patient = await PatientRepository().get_by_id(db, data.patient_id)
        if not patient:
            raise NotFoundException(message=f"Patient {data.patient_id} not found.")

        if not current_user.is_superadmin and patient.lab_id != current_user.lab_id:
            raise NotFoundException(message=f"Patient {data.patient_id} not found in this branch.")

        email_recipient: str | None = None
        if data.channel == NotificationChannel.EMAIL:
            if not normalize_email_address(patient.email):
                raise ValidationException(
                    message="This patient has no valid email address. Add one before sending email."
                )
            email_recipient = normalize_email_address(data.recipient)
            if not email_recipient:
                raise ValidationException(message="Enter a valid email recipient address.")

        notif = Notification(
            patient_id=patient.id,
            order_id=data.order_id,
            report_id=data.report_id,
            channel=data.channel,
            status=NotificationStatus.PENDING,
            recipient=email_recipient or data.recipient,
            subject=data.subject,
            message=data.message,
        )
        notif = await self.repo.create(db, notif)

        if data.channel == NotificationChannel.EMAIL:
            # Simple text/html payload for ad-hoc messages
            html = f"<div style='font-family: sans-serif; padding: 20px;'>{data.message}</div>"
            success, error_msg = await self.email_service.send_email(
                recipient=email_recipient or data.recipient,
                subject=data.subject or "Notification from Munal LMS",
                html_content=html,
            )
            if success:
                notif = await self.repo.update_status(
                    db, notif, status=NotificationStatus.SENT, sent_at=datetime.now(UTC)
                )
            else:
                notif = await self.repo.update_status(
                    db, notif, status=NotificationStatus.FAILED, error_message=error_msg
                )
        else:
            # SMS or Portal (simulated / logged)
            notif = await self.repo.update_status(
                db, notif, status=NotificationStatus.SENT, sent_at=datetime.now(UTC)
            )

        await db.commit()
        return self._to_response(notif)

    async def list_notifications(
        self,
        db: AsyncSession,
        current_user: User,
        pagination: PaginationParams,
        status: NotificationStatus | None = None,
        channel: NotificationChannel | None = None,
        patient_id: UUID | None = None,
    ) -> PaginatedResponse[NotificationResponse]:
        """List notifications with branch scoping and status filters."""
        lab_id = None if current_user.is_superadmin else current_user.lab_id
        items, total = await self.repo.list(
            db=db,
            limit=pagination.limit,
            offset=pagination.offset,
            lab_id=lab_id,
            patient_id=patient_id,
            channel=channel,
            status=status,
        )
        pages = math.ceil(total / pagination.limit) if pagination.limit > 0 else 1
        return PaginatedResponse(
            data=[self._to_response(item) for item in items],
            meta=PaginationMeta(
                total=total,
                page=pagination.page,
                limit=pagination.limit,
                pages=pages,
            ),
        )

    async def get_notification(
        self,
        db: AsyncSession,
        notification_id: UUID,
        current_user: User,
    ) -> NotificationResponse:
        """Fetch single notification details with branch validation."""
        notif = await self.repo.get_by_id(db, notification_id)
        if not notif:
            raise NotFoundException(message=f"Notification {notification_id} not found.")

        if not current_user.is_superadmin and notif.patient and notif.patient.lab_id != current_user.lab_id:
            raise NotFoundException(message=f"Notification {notification_id} not found in this branch.")

        return self._to_response(notif)
