#!/usr/bin/env python3
import os
import imaplib
import email
from email.header import decode_header, make_header
from email.utils import parsedate_to_datetime, parseaddr
from dotenv import load_dotenv

# requires: uv add python-dotenv
#           credentials.env with IMAP_HOST, IMAP_USER, IMAP_PASS

# load credentials
load_dotenv("credentials.env")
HOST = os.environ["IMAP_HOST"]
USER = os.environ["IMAP_USER"]
PASS = os.environ["IMAP_PASS"]


# decode RFC-2047 encoded headers (e.g. =?utf-8?q?...?=)
def decode_mime(value: str | None) -> str:
    return str(make_header(decode_header(value or "")))


# clip text to max_len, appending ellipsis if truncated
def truncate(text: str, n: int) -> str:
    return text if len(text) <= n else text[: n - 3] + "..."


with imaplib.IMAP4_SSL(HOST) as imap:
    imap.login(USER, PASS)
    # readonly=True: no side effects on message state
    imap.select("INBOX", readonly=True)

    # fetch all IDs, format as byte string for fetch() 
    _, data = imap.search(None, "ALL")
    id_set = b",".join(data[0].split()[-10:]) # max. 10 
    # also get unseen IDs 
    _, unseen_data = imap.search(None, "UNSEEN")
    unseen = set(unseen_data[0].split())

    # get relevant data (PEEK avoids setting the Seen flag)
    fields = "(FLAGS BODY.PEEK[HEADER.FIELDS (DATE FROM SUBJECT)])"
    _, rows = imap.fetch(id_set, fields)

    # decode and format data
    messages = []
    for row in rows:
        if not isinstance(row, tuple):
            continue
        meta, raw_headers = row
        # sequence number at start of meta (e.g. b'4 (FLAGS ...')
        seq    = meta.split()[0]
        is_new = seq in unseen
        msg     = email.message_from_bytes(raw_headers)
        dt      = parsedate_to_datetime(msg["Date"])
        date    = dt.strftime("%d-%m %H:%M")
        addr    = parseaddr(decode_mime(msg.get("From", "")))[1]
        sender  = truncate(addr, 20)
        raw_sub = decode_mime(msg.get("Subject", "(no subject)"))
        subject = truncate(raw_sub, 40)
        messages.append((is_new, date, sender, subject))

    # reverse so newest appears first
    for is_new, date, sender, subject in reversed(messages):
        flag = "*" if is_new else " "
        print(f"{flag} {date}  {sender:<20}  {subject}")
