# RadicaleIMAP IMAP authentication plugin for Radicale.
# Copyright © 2017, 2020 Unrud <unrud@outlook.com>
# Copyright © 2025-2025 Peter Bieringer <pb@bieringer.de>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import imaplib
import ssl
import sys
from typing import Union

from radicale import auth
from radicale.log import logger


class Auth(auth.BaseAuth):
    """Authenticate user with IMAP."""

    def __init__(self, configuration) -> None:
        super().__init__(configuration)
        self._host, self._port = self.configuration.get("auth", "imap_host")
        logger.info("auth imap host: %r", self._host)
        self._security = self.configuration.get("auth", "imap_security")
        if self._security == "none":
            logger.warning("auth imap security: %s (INSECURE, credentials are transmitted in clear text)", self._security)
        else:
            logger.info("auth imap security: %s", self._security)
        if self._security == "tls":
            if self._port is None:
                self._port = 993
                logger.info("auth imap port (autoselected): %d", self._port)
            else:
                logger.info("auth imap port: %d", self._port)
        else:
            if self._port is None:
                self._port = 143
                logger.info("auth imap port (autoselected): %d", self._port)
            else:
                logger.info("auth imap port: %d", self._port)
        self._append_domain = self.configuration.get("auth", "imap_append_domain")
        if self._append_domain is None or len(self._append_domain) == 0:
            logger.info("auth imap append domain not used")
        else:
            logger.info("auth imap append domain: %s", self._append_domain)

    def _login(self, login, password) -> str:
        try:
            if sys.version_info < (3, 10):
                connection: Union[imaplib.IMAP4, imaplib.IMAP4_SSL]
            else:
                connection: imaplib.IMAP4 | imaplib.IMAP4_SSL
            if self._security == "tls":
                connection = imaplib.IMAP4_SSL(
                    host=self._host, port=self._port,
                    ssl_context=ssl.create_default_context())
            else:
                connection = imaplib.IMAP4(host=self._host, port=self._port)
                if self._security == "starttls":
                    connection.starttls(ssl.create_default_context())
            if self._append_domain is None or len(self._append_domain) == 0:
                imaplogin = login
            else:
                imaplogin = login + "@" + self._append_domain
            try:
                if "AUTH=PLAIN" in connection.capabilities:
                    logger.debug("IMAP authentication PLAIN selected for user %r via %s:%d (security: %s)", imaplogin, self._host, self._port, self._security)
                    connection.authenticate(
                        "PLAIN",
                        lambda _: "{0}\x00{0}\x00{1}".format(imaplogin, password).encode(),
                    )
                elif "AUTH=LOGIN" in connection.capabilities:
                    logger.debug("IMAP authentication LOGIN selected for user %r via %s:%d (security: %s)", imaplogin, self._host, self._port, self._security)
                    connection.login(imaplogin, password)
                else:
                    logger.error("IMAP server is neither supporting AUTH=PLAIN or AUTH=LOGIN: %s:%d (security: %s)", self._host, self._port, self._security)
                    return ""
            except imaplib.IMAP4.error as e:
                logger.warning("IMAP authentication failed for user %r: %s", imaplogin, e, exc_info=False)
                return ""
            connection.logout()
            return login
        except (OSError, imaplib.IMAP4.error) as e:
            logger.error("Failed to communicate with IMAP server %r: %s" % ("[%s]:%d" % (self._host, self._port), e))
            return ""
