"""
Cabinet Mail

Provides functionality for sending email using SMTP and MIMEText.
Does not support Gmail.

A throwaway email is highly recommended.
"""

import smtplib
from urllib.parse import unquote
import socket
import pwd
import sys
import os
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from prompt_toolkit import print_formatted_text, HTML
import cabinet


class Mail:
    """
    Provides functionality for sending email using SMTP and MIMEText.
    Does not support Gmail.
    A throwaway email is highly recommended.
    """

    def __init__(self):
        self.user_dir = pwd.getpwuid(os.getuid())[0]
        self.cab = cabinet.Cabinet()

        # parameters
        # port should be int; TODO update Cabinet to support type casting
        self.port = self.cab.get("email", "port")

        self.smtp_server: str | None = self.cab.get("email", "smtp_server")
        self.imap_server: str | None = self.cab.get("email", "imap_server")
        self.username = self.cab.get("email", "from")
        self.password = self.cab.get("email", "from_pw")

    def send(
        self,
        subject: str,
        body: str,
        signature: str = "",
        to_addr=None,  # should be List[str]; TODO update Cabinet to support type casting
        from_name: str | None = None,
        logging_enabled: bool = True,
        is_quiet: bool = False,
        timeout: int = 4,
    ) -> None:
        """
        Sends an email with the given subject and body to the specified recipients.

        Args:
        - subject (str): The subject of the email.
        - body (str): The body of the email.
        - signature (str): The signature to include at the end of the email.
        - to_addr (List): A list of email addresses to send the email to.
        - from_name (str, optional): The name to appear in the "From" field of the email.
            Reads from cabinet -> email -> from_name if unset.
            If this is unset, defaults to machine's hostname or `Cabinet`
        - logging_enabled (bool, optional): Whether to log the email send event.
            Defaults to True.
        - is_quiet: Whether to suppress log output.
            Defaults to False.
        - timeout (int, optional): Timeout in seconds for SMTP operations.
            Defaults to 4 seconds.

        Raises:
        - smtplib.SMTPAuthenticationError: If the SMTP server rejects the login credentials.
        - socket.timeout: If the SMTP operations exceed the timeout period.

        Gmail will almost certainly not work.
        """

        hostname = socket.gethostname()
        if hostname:
            hostname = hostname.capitalize().replace(".local", "")

        cab_from_name = self.cab.get("email", "from_name") or hostname or "Cabinet"

        # send IP if reminder came directly from outside of server
        client_name = os.getenv("SSH_CONNECTION")

        if client_name:
            client_name = (
                client_name.strip().replace("\n", "").replace("\r", "").split(" ")[0]
            )
        else:
            client_name = ""

        email_from = f"{cab_from_name}<br>{client_name}"

        # Set default `from_name` if unset.
        if from_name is None:
            from_name = f"{cab_from_name} <{self.username}>"

        # Debug: Log the to_addr parameter as received
        self.cab.log(
            f"Mail.send() received to_addr: {to_addr} (type: {type(to_addr).__name__})",
            level="debug"
        )

        # Set default `to_addr` if unset.
        if to_addr is None:
            to_addr = self.cab.get("email", "to")
            self.cab.log(
                f"Mail.send() using default from config: {to_addr}",
                level="debug"
            )

            if to_addr is None:
                self.cab.log("cabinet -> email -> to is unset", level="error")
                return

        # Ensure to_addr is a list
        # Cabinet may return a string, but we need a list for join()
        if isinstance(to_addr, str):
            to_addr = [to_addr]

        # Remove duplicates while preserving order
        seen = set()
        to_addr = [addr for addr in to_addr if addr not in seen and not seen.add(addr)]

        # Debug: Log the final to_addr being used
        self.cab.log(
            f"Mail.send() final to_addr: {to_addr} (type: {type(to_addr).__name__})",
            level="debug"
        )

        # Append `signature` to the `body` of the email.
        signature = signature or f"<br><br>Thanks,<br>{email_from}"
        body += unquote(signature)

        # Create the message object.
        message = MIMEMultipart()
        message["Subject"] = unquote(subject)
        message["From"] = from_name
        message["To"] = (", ").join(to_addr)
        message.attach(MIMEText(unquote(body), "html"))

        if not self.smtp_server:
            self.cab.log("No SMTP Server set", level="error")
            return

        if not self.port:
            self.cab.log("No port set", level="error")
            return

        if not isinstance(self.port, int):
            self.cab.log(
                f"Port is not an integer (received '{self.port}')", level="error"
            )
            return

        # Port 465 uses SSL/TLS directly, port 587 uses STARTTLS
        server = None
        try:
            if self.port == 465:
                server = smtplib.SMTP_SSL(self.smtp_server, self.port, timeout=timeout)
            elif self.port == 587:
                server = smtplib.SMTP(self.smtp_server, self.port, timeout=timeout)
                server.starttls()
            else:
                # Default to SSL for other ports (backward compatibility)
                server = smtplib.SMTP_SSL(self.smtp_server, self.port, timeout=timeout)

            if not self.username or not self.password:
                self.cab.log("Username/password not set", level="error")
                return
            
            # Debug: Log the username being used (without password)
            self.cab.log(
                f"Attempting SMTP login with username: {self.username}",
                level="debug"
            )
            
            server.login(self.username, self.password)

            # Send the email.
            server.send_message(message)

            if logging_enabled:
                self.cab.log(
                    f"Sent Email to {message['To']} as {message['From']}: {subject}",
                    level="debug",
                )
            if not is_quiet:
                print_formatted_text(HTML("<ansigreen><b>Email sent.</b></ansigreen>"))

        except smtplib.SMTPAuthenticationError as err:
            self.cab.log(
                f"SMTP authentication failed for {self.username}.\n"
                f"Error: {err}\n"
                f"For Proton Mail, ensure:\n"
                f"  1. Username matches the email address used when generating the SMTP token\n"
                f"  2. Password is the SMTP token (not your regular password)\n"
                f"  3. Token was generated for the correct email address",
                level="error",
            )
        except (socket.timeout, smtplib.SMTPServerDisconnected) as err:
            self.cab.log(
                f"SMTP connection failed after {timeout} seconds: {err}", level="error"
            )
        finally:
            # Always close the SMTP connection to prevent connection reuse issues
            if server:
                try:
                    server.quit()
                except Exception:
                    # If quit() fails, try close() as fallback
                    try:
                        server.close()
                    except Exception:
                        pass  # Connection may already be closed


if __name__ == "__main__":
    if len(sys.argv) == 1:
        print("Usage: cabinet --mail -s <subject> -b <body> --to <to, optional>")
