"""
Notifications services — business logic for sending notifications.

Resolves the correct template from site_templates, renders it with Jinja2,
merges merchant branding, selects the appropriate provider, and dispatches
the message.

Stubs:
  - If SENDGRID_API_KEY is missing, email is logged only.
  - If TWILIO credentials are missing, SMS is logged only.
"""

import logging
from typing import Any, Dict, List, Optional

from sqlalchemy import select
from sqlalchemy.orm import Session

from src.apps.notifications.schemas.notification_schemas import (
    NotificationResultSchema,
    SendNotificationSchema,
)
from src.apps.notifications.providers.sendgrid import SendGridProvider
from src.apps.notifications.providers.twilio import TwilioProvider

logger = logging.getLogger(__name__)

# Singleton provider instances (no state; safe to reuse)
_email_provider = SendGridProvider()
_sms_provider = TwilioProvider()


def _get_merchant_branding(db: Session, merchant_id: Optional[int]) -> Dict[str, str]:
    """
    Fetch branding values (primary_color, accent_color, logo_url, merchant_name)
    for the given merchant using the supplied DB session.

    Does NOT open a new SessionCelery — uses the session passed by the caller
    so that Celery tasks that already hold a session avoid nesting.

    Returns an empty dict if merchant_id is None or settings are not found.
    """
    if merchant_id is None:
        return {}

    try:
        from src.apps.merchants.models.merchant_settings import MerchantSettings
        from src.apps.settings.defaults import MERCHANT_SETTINGS_DEFAULTS

        color_defaults = MERCHANT_SETTINGS_DEFAULTS.get("Colors", {})
        logo_defaults = MERCHANT_SETTINGS_DEFAULTS.get("Logo", {})

        def _get(group: str, key: str, default: str = "") -> str:
            stmt = select(MerchantSettings.value).where(
                MerchantSettings.merchant_id == merchant_id,
                MerchantSettings.group == group,
                MerchantSettings.key == key,
            )
            row = db.execute(stmt).scalar_one_or_none()
            if row is not None:
                return row
            return color_defaults.get(key) or logo_defaults.get(key) or default

        branding: Dict[str, str] = {
            "primary_color": _get("Colors", "primary_color", "#28B4ED"),
            "accent_color": _get("Colors", "accent_color", "#FB7585"),
            "text_color": _get("Colors", "secondary_color", "#252525"),
            "logo_url": _get("Logo", "logo_url", ""),
        }

        # Fetch merchant name
        try:
            from src.apps.merchants.models.merchant import Merchant
            name_row = db.execute(
                select(Merchant.name).where(
                    Merchant.id == merchant_id,
                    Merchant.deleted_at.is_(None),
                )
            ).scalar_one_or_none()
            if name_row:
                branding["merchant_name"] = name_row
        except Exception:
            pass

        return branding

    except Exception as exc:
        logger.warning("_get_merchant_branding failed for merchant_id=%s: %s", merchant_id, exc)
        return {}


async def send_notification(
    db: Session,
    notification: SendNotificationSchema,
) -> NotificationResultSchema:
    """
    Resolve template from site_templates, render body with Jinja2, merge
    merchant branding, and dispatch via the appropriate provider.

    Args:
        db:           Database session.
        notification: Notification request with template_key, channel, recipient,
                      and template_vars.

    Returns:
        NotificationResultSchema indicating success or failure.
    """
    from src.apps.site_templates import crud as site_template_crud
    from src.apps.site_templates import services as site_template_services

    template = site_template_crud.get_template_by_key(db, notification.template_key)

    if not template or not template.is_active:
        logger.warning(
            "No active site_template found: key=%s channel=%s merchant_id=%s",
            notification.template_key,
            notification.channel,
            notification.merchant_id,
        )
        return NotificationResultSchema(
            success=False,
            channel=notification.channel,
            recipient=notification.recipient_email or notification.recipient_phone or "",
            template_key=notification.template_key,
            error="Template not found",
            message="Notification skipped — template not found",
        )

    # Build render variables: branding first, then caller-supplied vars override.
    branding = _get_merchant_branding(db, notification.merchant_id)
    template_vars = notification.template_vars or {}
    render_vars: Dict[str, Any] = {**branding, **template_vars}

    rendered = site_template_services.render_template(template, render_vars)

    rendered_subject = template.subject or ""
    rendered_body_html = rendered["html"]
    rendered_body_text = rendered["text"]

    success = False
    recipient = ""
    error_msg = None

    try:
        if notification.channel == "email":
            recipient = notification.recipient_email or ""
            if not recipient:
                raise ValueError("No recipient_email provided for email notification")
            success = await _email_provider.send(
                recipient=recipient,
                subject=rendered_subject,
                body_text=rendered_body_text,
                body_html=rendered_body_html,
                template_vars=render_vars,
            )

        elif notification.channel == "sms":
            recipient = notification.recipient_phone or ""
            if not recipient:
                raise ValueError("No recipient_phone provided for sms notification")
            success = await _sms_provider.send(
                recipient=recipient,
                subject=None,
                body_text=rendered_body_text,
                body_html=None,
                template_vars=render_vars,
            )

        else:
            raise ValueError(f"Unknown notification channel: {notification.channel}")

    except Exception as exc:
        # Log the full exception internally but do NOT surface raw exception
        # text to the caller — it may contain connection strings, credentials,
        # or other internal details.
        logger.error(
            "Notification send failed [%s/%s] to %s: %s",
            notification.template_key,
            notification.channel,
            recipient,
            exc,
        )
        error_msg = "Notification dispatch failed"

    return NotificationResultSchema(
        success=success,
        channel=notification.channel,
        recipient=recipient,
        template_key=notification.template_key,
        error=error_msg,
        message="Notification dispatched" if success else "Notification failed",
    )


async def send_hpp_payment_request_notification(
    db: Session,
    merchant_id: int,
    merchant_name: str,
    customer_email: Optional[str],
    customer_phone: Optional[str],
    amount: float,
    currency: str,
    due_date: Optional[str],
    hpp_link: str,
) -> None:
    """
    Send HPP payment request notification (email + SMS) to the customer.
    """
    vars_ = {
        "merchant_name": merchant_name,
        "amount": f"{amount:.2f}",
        "currency": currency,
        "due_date": due_date or "",
        "hpp_link": hpp_link,
    }

    if customer_email:
        await send_notification(
            db,
            SendNotificationSchema(
                template_key="hpp_payment_request",
                channel="email",
                recipient_email=customer_email,
                template_vars=vars_,
                merchant_id=merchant_id,
            ),
        )

    if customer_phone:
        await send_notification(
            db,
            SendNotificationSchema(
                template_key="hpp_payment_request_sms",
                channel="sms",
                recipient_phone=customer_phone,
                template_vars=vars_,
                merchant_id=merchant_id,
            ),
        )


async def send_payment_receipt_notification(
    db: Session,
    merchant_id: int,
    merchant_name: str,
    customer_email: Optional[str],
    customer_phone: Optional[str],
    amount: float,
    currency: str,
    transaction_id: str,
) -> None:
    """
    Send payment receipt notification (email + SMS).
    """
    vars_ = {
        "merchant_name": merchant_name,
        "amount": f"{amount:.2f}",
        "currency": currency,
        "transaction_id": transaction_id,
    }

    if customer_email:
        await send_notification(
            db,
            SendNotificationSchema(
                template_key="transaction_receipt_email",
                channel="email",
                recipient_email=customer_email,
                template_vars=vars_,
                merchant_id=merchant_id,
            ),
        )

    if customer_phone:
        await send_notification(
            db,
            SendNotificationSchema(
                template_key="transaction_receipt_email",
                channel="sms",
                recipient_phone=customer_phone,
                template_vars=vars_,
                merchant_id=merchant_id,
            ),
        )


async def send_payment_failed_retry_notification(
    db: Session,
    merchant_id: int,
    merchant_name: str,
    customer_email: Optional[str],
    customer_phone: Optional[str],
    amount: float,
    currency: str,
    due_date: Optional[str],
    retry_link: str,
) -> None:
    """
    Send payment-failed retry notification (email + SMS).
    """
    vars_ = {
        "merchant_name": merchant_name,
        "amount": f"{amount:.2f}",
        "currency": currency,
        "due_date": due_date or "",
        "retry_url": retry_link,
    }

    if customer_email:
        await send_notification(
            db,
            SendNotificationSchema(
                template_key="payment_failed",
                channel="email",
                recipient_email=customer_email,
                template_vars=vars_,
                merchant_id=merchant_id,
            ),
        )

    if customer_phone:
        await send_notification(
            db,
            SendNotificationSchema(
                template_key="payment_failed_retry_sms",
                channel="sms",
                recipient_phone=customer_phone,
                template_vars=vars_,
                merchant_id=merchant_id,
            ),
        )


async def send_customer_account_created_notification(
    db: Session,
    merchant_id: int,
    merchant_name: str,
    customer_email: str,
    customer_name: str,
    verify_link: str,
) -> None:
    """
    Send welcome + email-verification notification after customer account creation.
    """
    vars_ = {
        "merchant_name": merchant_name,
        "customer_name": customer_name,
        "verification_url": verify_link,
    }
    await send_notification(
        db,
        SendNotificationSchema(
            template_key="account_verification",
            channel="email",
            recipient_email=customer_email,
            template_vars=vars_,
            merchant_id=merchant_id,
        ),
    )
