#!/usr/bin/env python3
"""
Monitoring plugin to check local unread email presence

Author : Benjamin Renard <brenard@easter-eggs.com>
Date : Mon, 20 Jan 2020 12:59:14 +0100
Source : https://gitlab.easter-eggs.com/check_local_unread_emails
"""

import logging
import os
import sys

# Configuration
desc = "Monitoring tool to check local unread email presence"

default_spool_dir_path = "/var/mail"

# Arguments
try:
    import argparse

    parser = argparse.ArgumentParser(description=desc)

    parser.add_argument(
        "-D",
        dest="spool_dir_path",
        type=str,
        help=f"Spool directory path (Default: {default_spool_dir_path})",
        default=default_spool_dir_path,
    )

    parser.add_argument(
        "-x",
        "--exclude",
        action="append",
        type=str,
        help="Exclude one (or more) mailboxes from monitoring",
    )

    parser.add_argument("-d", "--debug", action="store_true", help="Show debug messages")

    parser.add_argument("-v", "--verbose", action="store_true", help="Show verbose messages")

    parser.add_argument("-w", "--warning", action="store_true", help="Show warning messages")

    parser.add_argument("-l", "--logfile", action="store", type=str, help="Log file path")

    parser.add_argument(
        "-C",
        "--console",
        action="store_true",
        help="Also log on console (even if log file is provided)",
    )

    options = parser.parse_args()
except ImportError:
    import optparse  # pylint: disable=deprecated-module

    parser = optparse.OptionParser(description=desc)

    parser.add_option(
        "-D",
        dest="spool_dir_path",
        type="string",
        help=f"Spool directory path (Default: {default_spool_dir_path}",
        default=default_spool_dir_path,
    )

    parser.add_option(
        "-x",
        "--exclude",
        dest="exclude",
        action="append",
        type="string",
        help="Exclude one (or more) mailboxes from monitoring",
    )

    parser.add_option("-d", "--debug", action="store_true", help="Show debug messages")

    parser.add_option("-v", "--verbose", action="store_true", help="Show verbose messages")

    parser.add_option("-w", "--warning", action="store_true", help="Show warning messages")

    parser.add_option("-l", "--logfile", action="store", type="string", help="Log file path")

    parser.add_option(
        "-C",
        "--console",
        action="store_true",
        help="Also log on console (even if log file is provided)",
    )

    (options, args) = parser.parse_args()

# Initialize log
log = logging.getLogger()
logformat = logging.Formatter(
    "%(asctime)s - " + os.path.basename(sys.argv[0]) + " - %(levelname)s : %(message)s"
)

if options.debug:
    log.setLevel(logging.DEBUG)
elif options.verbose:
    log.setLevel(logging.INFO)
elif options.warning:
    log.setLevel(logging.WARNING)
else:
    log.setLevel(logging.FATAL)

if options.logfile:
    logfile = logging.FileHandler(options.logfile)
    logfile.setFormatter(logformat)
    log.addHandler(logfile)

if not options.logfile or options.console:
    logconsole = logging.StreamHandler()
    logconsole.setFormatter(logformat)
    log.addHandler(logconsole)

if not os.path.isdir(options.spool_dir_path):
    print(f"UNKNOWN - Spool directory {options.spool_dir_path} not found (or not a directory)")
    sys.exit(3)

unread_msg_users = {}

# Initialize these variables to avoid pylint warnings
total_msg = read_msg = unread_msg = 0


def handle_msg_info():
    """Handle message info after detecting a new one (or at the end)"""
    global total_msg, read_msg, unread_msg  # pylint: disable=global-statement
    if not current_message:
        return
    log.debug('Handle msg "%s" info (Status = %s)', current_message, status)
    if status is None:
        # Status not defined : it's a new message
        unread_msg += 1
        unread_messages.append(current_message)
    elif "D" in status:
        # Message is deleted : do not count it
        pass
    elif "R" in status:
        read_msg += 1
    else:
        unread_msg += 1
        unread_messages.append(current_message)
    total_msg += 1


errors = []
log.debug("List mbox files from %s directory", options.spool_dir_path)
for mbox_file in os.listdir(options.spool_dir_path):
    mbox_path = os.path.join(options.spool_dir_path, mbox_file)
    if not os.path.isfile(mbox_path):
        continue
    if options.exclude and mbox_file in options.exclude:
        log.debug("Mailbox %s is excluded", mbox_file)
        continue
    log.debug("Handling %s mbox file", mbox_file)
    try:
        total_msg = 0
        read_msg = 0
        unread_msg = 0
        unread_messages = []

        current_message = False
        in_headers = True
        status = None
        with open(mbox_path, encoding="utf8") as mbox_fd:
            for line in mbox_fd.readlines():
                if line.startswith("From "):
                    handle_msg_info()
                    current_message = line.strip()
                    in_headers = True
                    status = None
                elif current_message and in_headers and line.strip() == "":
                    log.debug("End of headers detected")
                    in_headers = False
                elif (
                    current_message
                    and in_headers
                    and status is None
                    and line.startswith("Status: ")
                ):
                    status = line[8:].strip()
        handle_msg_info()
        if unread_msg:
            unread_msg_users[mbox_file] = {
                "total": total_msg,
                "read": read_msg,
                "unread": unread_msg,
                "unread_messages": unread_messages,
            }
        log.debug(
            "Mbox file %s contains %d email(s) including %d unread and %d read messages",
            mbox_file,
            total_msg,
            unread_msg,
            read_msg,
        )
    except OSError:
        log.warning("Fail to read MBOX file %s", mbox_file, exc_info=True)
        errors.append(mbox_file)
if errors:
    print(f"UNKNOWN - Error occured reading MBOX file(s) {', '.join(errors)}")
elif unread_msg_users:
    # pylint: disable=consider-using-f-string
    print(
        "WARNING - Local unread email found for the following(s) user(s): %s\n"
        % ", ".join(
            [
                f"{user} ({details['unread']} unread messages on {details['total']})"
                for user, details in unread_msg_users.items()
            ]
        )
    )

    for user, details in unread_msg_users.items():
        msg = f"Unread email of user {user} "
        if len(details["unread_messages"]) > 10:
            msg += "(last 10 messages) :\n"
            msg += "\n".join(details["unread_messages"][-10:])
        else:
            msg += ":\n" + "\n".join(details["unread_messages"])
        print(msg + "\n")
else:
    print("OK - No local unread email found")
    sys.exit(0)

if not errors:
    sys.exit(1)
sys.exit(3)
