"""Email notification support with Gmail API and SMTP fallback."""

import base64
import binascii
import os
import re
import smtplib
import tempfile
import uuid
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from typing import Any, Dict, List, Optional, Tuple, Union

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build

from cicaddy.utils.logger import get_logger

from .base import BaseNotifier

logger = get_logger(__name__)


class EmailNotifier(BaseNotifier):
    """Email notifier with Gmail API primary and SMTP fallback support."""

    def __init__(
        self,
        recipients: str | List[str],
        sender_email: Optional[str] = None,
        use_gmail_api: bool = True,
        smtp_config: Optional[Dict[str, Any]] = None,
        ssl_verify: bool = True,
    ):
        """Initialize email notifier.

        Args:
            recipients: Email recipient(s) - comma-separated string or list
            sender_email: Sender email address (defaults to Gmail authenticated user)
            use_gmail_api: Whether to use Gmail API (True) or SMTP (False)
            smtp_config: SMTP configuration dict with keys:
                - host: SMTP server host
                - port: SMTP server port
                - username: SMTP username
                - password: SMTP password
                - use_tls: Whether to use TLS (default: True)
            ssl_verify: Whether to verify SSL certificates
        """
        # BaseNotifier expects webhook_url, but we don't use it for email
        super().__init__("", ssl_verify)

        # Parse recipients
        if isinstance(recipients, str):
            self.recipients = [r.strip() for r in recipients.split(",")]
        else:
            self.recipients = recipients

        self.sender_email = sender_email
        self.use_gmail_api = use_gmail_api
        self.smtp_config = smtp_config or {}

        # Gmail API service (lazy initialization)
        self._gmail_service = None

    async def send_notification(self, message: str, **kwargs) -> Dict[str, Any]:
        """Send email notification.

        Args:
            message: Email message content (can be plain text or HTML)
            **kwargs:
                - subject: Email subject (required)
                - html: Whether message is HTML (default: False)
                - cc: CC recipients (comma-separated or list)
                - bcc: BCC recipients (comma-separated or list)
                - attachments: List of attachment dicts with 'filename' and 'content'

        Returns:
            Dict with status and details
        """
        subject = kwargs.get("subject", "Cicaddy Notification")
        is_html = kwargs.get("html", False)
        cc = kwargs.get("cc", [])
        bcc = kwargs.get("bcc", [])

        # Parse CC and BCC
        if isinstance(cc, str):
            cc = [c.strip() for c in cc.split(",") if c.strip()]
        if isinstance(bcc, str):
            bcc = [b.strip() for b in bcc.split(",") if b.strip()]

        # Add autogenerated footer
        message_with_footer = self._add_email_footer(message, is_html)

        try:
            if self.use_gmail_api:
                logger.info("Sending email via Gmail API")
                return await self._send_via_gmail_api(
                    subject, message_with_footer, is_html, cc, bcc, kwargs
                )
            else:
                logger.info("Sending email via SMTP")
                return await self._send_via_smtp(
                    subject, message_with_footer, is_html, cc, bcc, kwargs
                )
        except Exception as e:
            logger.error(f"Failed to send email: {e}")
            return {
                "status": "failed",
                "error": str(e),
                "recipients": self.recipients,
            }

    async def _send_via_gmail_api(
        self,
        subject: str,
        message: str,
        is_html: bool,
        cc: List[str],
        bcc: List[str],
        kwargs: Dict[str, Any],
    ) -> Dict[str, Any]:
        """Send email using Gmail API with Base64-encoded credentials."""
        # Read Base64-encoded credentials from env vars
        credentials_b64 = os.getenv("GMAIL_CREDENTIALS_B64")
        token_b64 = os.getenv("GMAIL_TOKEN_B64")

        if not credentials_b64 or not token_b64:
            logger.error(
                "Missing Gmail API credentials. Set GMAIL_CREDENTIALS_B64 and GMAIL_TOKEN_B64 env vars"
            )
            return {
                "status": "failed",
                "error": "Missing Gmail API credentials",
                "recipients": self.recipients,
            }

        # Decode credentials to temp files
        temp_creds = None
        temp_token = None
        try:
            # Set restrictive file permissions for credential files (owner read/write only)
            old_umask = os.umask(0o077)

            # Create temporary files for credentials
            with tempfile.NamedTemporaryFile(
                mode="w", delete=False, suffix=".json"
            ) as f:
                credentials_json = base64.b64decode(credentials_b64).decode("utf-8")
                f.write(credentials_json)
                temp_creds = f.name

            with tempfile.NamedTemporaryFile(
                mode="w", delete=False, suffix=".json"
            ) as f:
                token_json = base64.b64decode(token_b64).decode("utf-8")
                f.write(token_json)
                temp_token = f.name

            # Restore original umask
            os.umask(old_umask)

            # Load token
            creds = Credentials.from_authorized_user_file(
                temp_token, ["https://www.googleapis.com/auth/gmail.send"]
            )

            # Refresh token if expired
            if creds and creds.expired and creds.refresh_token:
                logger.debug("Refreshing expired Gmail token")
                creds.refresh(Request())

                # Save refreshed token back to temp file
                with open(temp_token, "w") as f:
                    f.write(creds.to_json())

            # Build Gmail service
            service = build("gmail", "v1", credentials=creds)

            # Create email message
            mime_message = self._create_mime_message(
                subject, message, is_html, cc, bcc, kwargs
            )

            # Encode message
            encoded_message = base64.urlsafe_b64encode(mime_message.as_bytes()).decode()

            # Send message
            send_message = (
                service.users()
                .messages()
                .send(userId="me", body={"raw": encoded_message})
            )
            result = send_message.execute()

            logger.info(
                f"Email sent successfully via Gmail API. Message ID: {result.get('id')}"
            )

            return {
                "status": "sent",
                "method": "gmail_api",
                "message_id": result.get("id"),
                "recipients": self.recipients,
                "cc": cc,
                "bcc": bcc,
            }

        finally:
            # Clean up temp files
            if temp_creds and os.path.exists(temp_creds):
                os.unlink(temp_creds)
            if temp_token and os.path.exists(temp_token):
                os.unlink(temp_token)

    async def _send_via_smtp(
        self,
        subject: str,
        message: str,
        is_html: bool,
        cc: List[str],
        bcc: List[str],
        kwargs: Dict[str, Any],
    ) -> Dict[str, Any]:
        """Send email using SMTP.

        Supports three connection modes via use_tls and use_ssl config options:
        - use_ssl=True: SMTP_SSL for port 465 (implicit TLS)
        - use_tls=True: SMTP with STARTTLS for port 587
        - Both False: Plain SMTP for port 25 (no encryption)
        """
        # Get SMTP configuration from env vars or smtp_config
        smtp_host = self.smtp_config.get("host") or os.getenv("SMTP_HOST")
        smtp_port = self.smtp_config.get("port") or int(os.getenv("SMTP_PORT", "587"))
        smtp_user = self.smtp_config.get("username") or os.getenv("SMTP_USER")
        smtp_pass = self.smtp_config.get("password") or os.getenv("SMTP_PASSWORD")
        use_tls = self.smtp_config.get(
            "use_tls", os.getenv("SMTP_USE_TLS", "true").lower() == "true"
        )
        use_ssl = self.smtp_config.get(
            "use_ssl", os.getenv("SMTP_USE_SSL", "false").lower() == "true"
        )

        if not smtp_host:
            logger.error("Missing SMTP configuration. Set SMTP_HOST env var")
            return {
                "status": "failed",
                "error": "Missing SMTP configuration (SMTP_HOST required)",
                "recipients": self.recipients,
            }

        # Create MIME message
        mime_message = self._create_mime_message(
            subject, message, is_html, cc, bcc, kwargs
        )

        try:
            # Connect to SMTP server
            # Three modes:
            # - use_ssl=True: SMTP_SSL for port 465 (implicit TLS)
            # - use_tls=True: SMTP with STARTTLS for port 587
            # - both False: Plain SMTP for port 25 (no encryption)
            server: Union[smtplib.SMTP, smtplib.SMTP_SSL]
            if use_ssl:
                server = smtplib.SMTP_SSL(smtp_host, smtp_port, timeout=30)
            elif use_tls:
                server = smtplib.SMTP(smtp_host, smtp_port, timeout=30)
                server.starttls()
            else:
                # Plain SMTP without encryption (e.g., port 25)
                server = smtplib.SMTP(smtp_host, smtp_port, timeout=30)

            # Login only if credentials are provided (some SMTP servers don't require auth)
            if smtp_user and smtp_pass:
                server.login(smtp_user, smtp_pass)
            elif smtp_user or smtp_pass:
                logger.warning(
                    "Partial SMTP credentials provided (need both SMTP_USER and SMTP_PASSWORD). "
                    "Proceeding without authentication."
                )

            # Send email
            all_recipients = self.recipients + cc + bcc
            server.send_message(mime_message)
            server.quit()

            logger.info(
                f"Email sent successfully via SMTP to {len(all_recipients)} recipient(s)"
            )

            return {
                "status": "sent",
                "method": "smtp",
                "recipients": self.recipients,
                "cc": cc,
                "bcc": bcc,
                "smtp_host": smtp_host,
                "smtp_port": smtp_port,
            }

        except Exception as e:
            logger.error(f"SMTP send failed: {e}")
            return {
                "status": "failed",
                "error": str(e),
                "recipients": self.recipients,
                "smtp_host": smtp_host,
                "smtp_port": smtp_port,
            }

    def _create_mime_message(
        self,
        subject: str,
        message: str,
        is_html: bool,
        cc: List[str],
        bcc: List[str],
        kwargs: Dict[str, Any],
    ) -> MIMEMultipart:
        """Create MIME message for email with inline image support.

        For HTML emails with embedded base64 images, uses multipart/related structure:
        - multipart/related (root)
          - multipart/alternative
            - text/plain
            - text/html (with cid: references)
          - image/png (Content-ID: <chart_xxx>)
          - image/png (Content-ID: <chart_yyy>)
          - ...

        For HTML emails without images, uses simple multipart/alternative.
        For plain text emails, uses multipart/mixed.
        """
        sender = self.sender_email or os.getenv("SENDER_EMAIL")
        if not sender:
            raise ValueError(
                "Sender email must be provided via sender_email parameter "
                "or SENDER_EMAIL environment variable"
            )

        if is_html:
            # Extract base64 images and replace with CID references
            html_with_cid, images = self._extract_and_replace_base64_images(message)

            # Create plain-text fallback (from original HTML, before CID replacement)
            plain_text = self._html_to_plain_text(message)

            if images:
                # Use multipart/related for inline images
                msg_root = MIMEMultipart("related")
                msg_root["Subject"] = subject
                msg_root["From"] = sender
                msg_root["To"] = ", ".join(self.recipients)
                if cc:
                    msg_root["Cc"] = ", ".join(cc)

                # Create alternative part (text + HTML)
                msg_alt = MIMEMultipart("alternative")
                msg_alt.attach(MIMEText(plain_text, "plain", "utf-8"))
                msg_alt.attach(MIMEText(html_with_cid, "html", "utf-8"))
                msg_root.attach(msg_alt)

                # Attach images with Content-ID headers
                for cid, img_data, img_type in images:
                    img_part = MIMEImage(img_data, _subtype=img_type)
                    img_part.add_header("Content-ID", f"<{cid}>")
                    img_part.add_header(
                        "Content-Disposition", "inline", filename=f"{cid}.{img_type}"
                    )
                    msg_root.attach(img_part)
                    logger.debug(
                        f"Attached image: {cid}.{img_type} ({len(img_data) / 1024:.1f} KB)"
                    )

                return msg_root
            else:
                # No images - use simple alternative structure
                mime_message = MIMEMultipart("alternative")
                mime_message["Subject"] = subject
                mime_message["From"] = sender
                mime_message["To"] = ", ".join(self.recipients)
                if cc:
                    mime_message["Cc"] = ", ".join(cc)
                mime_message.attach(MIMEText(plain_text, "plain", "utf-8"))
                mime_message.attach(MIMEText(message, "html", "utf-8"))
                return mime_message
        else:
            # Plain text email
            mime_message = MIMEMultipart("mixed")
            mime_message["Subject"] = subject
            mime_message["From"] = sender
            mime_message["To"] = ", ".join(self.recipients)
            if cc:
                mime_message["Cc"] = ", ".join(cc)
            mime_message.attach(MIMEText(message, "plain", "utf-8"))
            return mime_message

    def _extract_and_replace_base64_images(
        self, html_content: str
    ) -> Tuple[str, List[Tuple[str, bytes, str]]]:
        """Extract base64 images from HTML and replace with CID references.

        Many email clients block or don't render base64 data URI images.
        This function converts them to CID (Content-ID) references which
        are properly supported when attached as MIME parts.

        Args:
            html_content: HTML string potentially containing base64 data URIs

        Returns:
            Tuple of (modified_html, list of (cid, image_bytes, img_type))
        """
        pattern = r'src="data:image/(png|jpeg|jpg|gif);base64,([^"]+)"'
        images: List[Tuple[str, bytes, str]] = []

        def replace_with_cid(match: re.Match) -> str:
            img_type = match.group(1)
            img_data = match.group(2)
            cid = f"chart_{uuid.uuid4().hex[:8]}"
            try:
                decoded_data = base64.b64decode(img_data)
                images.append((cid, decoded_data, img_type))
                return f'src="cid:{cid}"'
            except (binascii.Error, TypeError) as e:
                logger.warning(f"Failed to decode base64 image: {e}")
                return match.group(0)  # Keep original if decode fails

        modified_html = re.sub(pattern, replace_with_cid, html_content)

        if images:
            logger.info(f"Extracted {len(images)} base64 image(s) for CID attachment")

        return modified_html, images

    def _html_to_plain_text(self, html: str) -> str:
        """Convert HTML to plain text for multipart/alternative emails.

        Provides a basic text fallback for email clients that don't render HTML.
        """
        # Remove script and style elements
        text = re.sub(r"<script[^>]*>.*?</script>", "", html, flags=re.DOTALL | re.I)
        text = re.sub(r"<style[^>]*>.*?</style>", "", text, flags=re.DOTALL | re.I)

        # Replace common block elements with newlines
        text = re.sub(r"<br\s*/?>", "\n", text, flags=re.I)
        text = re.sub(r"</p>", "\n\n", text, flags=re.I)
        text = re.sub(r"</div>", "\n", text, flags=re.I)
        text = re.sub(r"</h[1-6]>", "\n\n", text, flags=re.I)
        text = re.sub(r"</li>", "\n", text, flags=re.I)
        text = re.sub(r"</tr>", "\n", text, flags=re.I)

        # Extract link URLs: <a href="url">text</a> -> text (url)
        text = re.sub(
            r'<a[^>]+href=["\']([^"\']+)["\'][^>]*>([^<]*)</a>',
            r"\2 (\1)",
            text,
            flags=re.I,
        )

        # Remove all remaining HTML tags
        text = re.sub(r"<[^>]+>", "", text)

        # Decode common HTML entities
        text = text.replace("&nbsp;", " ")
        text = text.replace("&amp;", "&")
        text = text.replace("&lt;", "<")
        text = text.replace("&gt;", ">")
        text = text.replace("&quot;", '"')
        text = text.replace("&#39;", "'")

        # Clean up whitespace
        text = re.sub(r"[ \t]+", " ", text)  # Collapse horizontal whitespace
        text = re.sub(r"\n{3,}", "\n\n", text)  # Limit consecutive newlines
        text = text.strip()

        return text

    def _add_email_footer(self, message: str, is_html: bool) -> str:
        """Add autogenerated footer with pipeline link."""
        ci_job_url = self._get_ci_job_url()
        ci_pipeline_url = os.getenv("CI_PIPELINE_URL", "")

        if is_html:
            footer = """
            <div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #e0e0e0; color: #666; font-size: 12px;">
                <p><strong>Autogenerated by Cicaddy</strong></p>
            """
            if ci_job_url:
                footer += f'<p>CI Job: <a href="{ci_job_url}">{ci_job_url}</a></p>'
            if ci_pipeline_url:
                footer += f'<p>Pipeline: <a href="{ci_pipeline_url}">{ci_pipeline_url}</a></p>'
            footer += """
            </div>
            """
            return message + footer
        else:
            footer = "\n\n" + "-" * 60 + "\n"
            footer += "Autogenerated by Cicaddy\n"
            if ci_job_url:
                footer += f"CI Job: {ci_job_url}\n"
            if ci_pipeline_url:
                footer += f"Pipeline: {ci_pipeline_url}\n"
            return message + footer
