# -*- coding: utf-8 -*-
"""Location: ./mcpgateway/services/email_notification_service.py
Copyright 2026
SPDX-License-Identifier: Apache-2.0
Authors: Mihai Criveti

Email notification service for authentication workflows.
"""

# Standard
import asyncio
from email.message import EmailMessage
from email.utils import formataddr
import html
from pathlib import Path
import re
import smtplib
import ssl
from typing import Any, Dict, Optional

# Third-Party
from jinja2 import Environment, FileSystemLoader, select_autoescape, TemplateNotFound

# First-Party
from mcpgateway.config import settings
from mcpgateway.services.logging_service import LoggingService

logging_service = LoggingService()
logger = logging_service.get_logger(__name__)


class AuthEmailNotificationService:
    """Send authentication-related email notifications."""

    def __init__(self) -> None:
        """Initialize template rendering for authentication emails."""
        template_dir = Path(__file__).resolve().parents[1] / "templates"
        self._jinja = Environment(loader=FileSystemLoader(str(template_dir)), autoescape=select_autoescape(["html", "xml"]))

    @staticmethod
    def _smtp_password() -> Optional[str]:
        """Resolve SMTP password from settings.

        Returns:
            Optional[str]: SMTP password string or None when unset.
        """
        raw = getattr(settings, "smtp_password", None)
        if raw is None:
            return None
        if hasattr(raw, "get_secret_value"):
            return raw.get_secret_value()
        return str(raw)

    @staticmethod
    def _smtp_ready() -> bool:
        """Check whether minimum SMTP settings are available.

        Returns:
            bool: True when SMTP delivery is enabled and required fields exist.
        """
        return bool(getattr(settings, "smtp_enabled", False) and getattr(settings, "smtp_host", None) and getattr(settings, "smtp_from_email", None))

    def _render_template(self, template_name: str, context: Dict[str, Any], fallback_title: str, fallback_body: str) -> str:
        """Render an email template with graceful fallback.

        Args:
            template_name: Jinja template filename.
            context: Rendering context values.
            fallback_title: Fallback HTML title when template fails.
            fallback_body: Fallback body text when template fails.

        Returns:
            str: Rendered HTML email body.
        """
        try:
            template = self._jinja.get_template(template_name)
            return template.render(**context)
        except TemplateNotFound:
            logger.warning("Email template %s not found. Using fallback template.", template_name)
        except Exception as exc:
            logger.warning("Failed to render email template %s: %s. Using fallback template.", template_name, exc)

        safe_title = html.escape(fallback_title)
        safe_body = html.escape(fallback_body).replace("\n", "<br/>")
        return f"<html><body><h2>{safe_title}</h2><p>{safe_body}</p></body></html>"

    @staticmethod
    def _html_to_text(value: str) -> str:
        """Convert simple HTML content to plain text.

        Args:
            value: HTML value.

        Returns:
            str: Text-only representation.
        """
        no_tags = re.sub(r"<[^>]+>", " ", value)
        compact = re.sub(r"\s+", " ", no_tags).strip()
        return compact

    async def _send_email(self, to_email: str, subject: str, html_body: str) -> bool:
        """Send an email asynchronously.

        Args:
            to_email: Destination email address.
            subject: Message subject.
            html_body: HTML message body.

        Returns:
            bool: True when message is sent successfully.
        """
        if not self._smtp_ready():
            logger.info("SMTP not configured. Skipping email to %s with subject '%s'.", to_email, subject)
            return False
        return await asyncio.to_thread(self._send_email_sync, to_email, subject, html_body)

    def _send_email_sync(self, to_email: str, subject: str, html_body: str) -> bool:
        """Send an email synchronously over SMTP.

        Args:
            to_email: Destination email address.
            subject: Message subject.
            html_body: HTML message body.

        Returns:
            bool: True when message is sent successfully.
        """
        from_email = str(getattr(settings, "smtp_from_email", "") or "")
        from_name = str(getattr(settings, "smtp_from_name", "ContextForge") or "ContextForge")
        smtp_user = getattr(settings, "smtp_user", None)
        smtp_password = self._smtp_password()

        message = EmailMessage()
        message["Subject"] = subject
        message["From"] = formataddr((from_name, from_email))
        message["To"] = to_email
        message.set_content(self._html_to_text(html_body))
        message.add_alternative(html_body, subtype="html")

        smtp_host = str(getattr(settings, "smtp_host", ""))
        smtp_port = int(getattr(settings, "smtp_port", 587))
        timeout_seconds = int(getattr(settings, "smtp_timeout_seconds", 15))
        use_ssl = bool(getattr(settings, "smtp_use_ssl", False))
        use_tls = bool(getattr(settings, "smtp_use_tls", True))

        try:
            if use_ssl:
                with smtplib.SMTP_SSL(smtp_host, smtp_port, timeout=timeout_seconds) as server:
                    if smtp_user and smtp_password:
                        server.login(str(smtp_user), smtp_password)
                    server.send_message(message)
            else:
                with smtplib.SMTP(smtp_host, smtp_port, timeout=timeout_seconds) as server:
                    server.ehlo()
                    if use_tls:
                        server.starttls(context=ssl.create_default_context())
                        server.ehlo()
                    if smtp_user and smtp_password:
                        server.login(str(smtp_user), smtp_password)
                    server.send_message(message)

            logger.info("Auth notification email sent to %s", to_email)
            return True
        except Exception as exc:
            logger.warning("Failed to send auth notification email to %s: %s", to_email, exc)
            return False

    async def send_password_reset_email(self, to_email: str, full_name: Optional[str], reset_url: str, expires_minutes: int) -> bool:
        """Send password-reset email containing a one-time reset link.

        Args:
            to_email: Destination email address.
            full_name: Optional display name for salutation.
            reset_url: Password-reset link.
            expires_minutes: Link validity duration.

        Returns:
            bool: True when message is sent successfully.
        """
        display_name = full_name or to_email.split("@", maxsplit=1)[0]
        subject = "Reset your ContextForge password"
        body = self._render_template(
            template_name="password_reset_email.html",
            context={"display_name": display_name, "reset_url": reset_url, "expires_minutes": expires_minutes, "recipient_email": to_email},
            fallback_title="Password reset requested",
            fallback_body=f"Hi {display_name},\n\nUse this link to reset your password: {reset_url}\n\nThe link expires in {expires_minutes} minutes.",
        )
        return await self._send_email(to_email, subject, body)

    async def send_password_reset_confirmation_email(self, to_email: str, full_name: Optional[str]) -> bool:
        """Send post-reset confirmation email.

        Args:
            to_email: Destination email address.
            full_name: Optional display name for salutation.

        Returns:
            bool: True when message is sent successfully.
        """
        display_name = full_name or to_email.split("@", maxsplit=1)[0]
        subject = "Your ContextForge password was changed"
        body = self._render_template(
            template_name="password_reset_confirmation_email.html",
            context={"display_name": display_name, "recipient_email": to_email},
            fallback_title="Password changed",
            fallback_body=f"Hi {display_name},\n\nYour password was changed successfully. If this was not you, contact an administrator immediately.",
        )
        return await self._send_email(to_email, subject, body)

    async def send_account_lockout_email(self, to_email: str, full_name: Optional[str], locked_until_iso: str, reset_url: str) -> bool:
        """Notify the user that login attempts triggered a temporary lockout.

        Args:
            to_email: Destination email address.
            full_name: Optional display name for salutation.
            locked_until_iso: ISO timestamp for lockout expiry.
            reset_url: Forgot-password URL for recovery.

        Returns:
            bool: True when message is sent successfully.
        """
        display_name = full_name or to_email.split("@", maxsplit=1)[0]
        subject = "Your ContextForge account was temporarily locked"
        body = self._render_template(
            template_name="account_lockout_email.html",
            context={"display_name": display_name, "locked_until": locked_until_iso, "reset_url": reset_url, "recipient_email": to_email},
            fallback_title="Account temporarily locked",
            fallback_body=f"Hi {display_name},\n\nYour account is locked until {locked_until_iso} due to repeated failed sign-in attempts.\n\nIf this was not you, reset your password now: {reset_url}",
        )
        return await self._send_email(to_email, subject, body)
