#!/usr/bin/env python3
"""
Herindeling van bestaande mail naar de nieuwe mappenstructuur.

Stap 1 – Vaste mapverplaatsingen:
         Uitgeschakeld. Alle niet-uitgesloten mappen worden als bron behandeld.

Stap 2 – Per-bericht routing:
         Op basis van afzenderdomein en onderwerpkeywords.
         Bronmappen: alles behalve Sent, Drafts, Trash, Spam en
         INBOX.Facturen - verwerkt.

Stap 3 – Facturen per kwartaal sorteren (Facturen - te verwerken).

Standaard: DRY RUN (laat zien wat er zou gebeuren, verplaatst niets).
Echt uitvoeren: python3 verplaats_bestaand.py --uitvoeren

Log: verplaats_log.json
"""

import email
import email.header
import imaplib
import sys
import json
import config
from pathlib import Path
from imap_utils import list_folders, quote_mailbox
from mail_routes import PREFIX, is_source_folder, quarter_folder, route_from_subject


LOG_FILE  = Path(__file__).parent / "verplaats_log.json"

DRY_RUN   = "--uitvoeren" not in sys.argv


# Mappen die in één keer worden hernoemd/verplaatst (oud → nieuw).
#
# Bewust leeg: oude mappen, archiefmappen en mailinglistmappen zijn bronnen.
# Alleen Sent, Drafts, Trash, Spam en INBOX.Facturen - verwerkt worden
# uitgesloten van routering.
MAP_RENAMES: list[tuple[str, str]] = [
]


# ── helpers ──────────────────────────────────────────────────────────────────

def decode_hdr(value) -> str:
    if not value:
        return ""
    try:
        parts = email.header.decode_header(value)
        out = []
        for raw, cs in parts:
            if isinstance(raw, bytes):
                out.append(raw.decode(cs or "utf-8", errors="replace"))
            else:
                out.append(str(raw))
        return " ".join(out).strip()
    except Exception:
        return str(value or "")


def list_source_folders(mail) -> list[str]:
    return [folder for folder in list_folders(mail) if is_source_folder(folder)]


def move_message(mail, uid: bytes, src: str, dst_full: str, dry: bool) -> bool:
    """Kopieer bericht naar dst en markeer als verwijderd in src."""
    if dry:
        return True
    try:
        mail.select(quote_mailbox(src))
        res, _ = mail.uid("COPY", uid, quote_mailbox(dst_full))
        if res != "OK":
            return False
        store_res, _ = mail.uid("STORE", uid, "+FLAGS", "\\Deleted")
        return store_res == "OK"
    except Exception as e:
        print(f"    FOUT bij verplaatsen: {e}")
        return False


# ── stap 1: vaste mapverplaatsingen ─────────────────────────────────────────

def stap1_mapverplaatsingen(mail, log: dict, dry: bool):
    print("\n── Stap 1: vaste mapverplaatsingen ──")
    for oud, nieuw in MAP_RENAMES:
        if nieuw == "__FACTUREN__":
            continue  # wordt in stap 3 verwerkt

        if oud in log.get("stap1_klaar", []):
            print(f"  ✓ al gedaan: {oud}")
            continue

        # Controleer of bronmap bestaat
        status, data = mail.select(quote_mailbox(oud), readonly=True)
        if status != "OK":
            print(f"  – bestaat niet: {oud}")
            continue

        n = int(data[0])
        nieuw_full = nieuw if nieuw.startswith("INBOX.") else PREFIX + nieuw

        if dry:
            print(f"  [DRY] {oud} → {nieuw}  ({n} berichten)")
            continue

        # Berichten kopiëren
        status, data = mail.search(None, "ALL")
        ids = data[0].split()
        gekopieerd = 0
        for uid in ids:
            res, _ = mail.copy(uid.decode(), quote_mailbox(nieuw_full))
            if res == "OK":
                mail.store(uid.decode(), "+FLAGS", "\\Deleted")
                gekopieerd += 1

        mail.expunge()
        print(f"  ✓ {oud} → {nieuw}  ({gekopieerd}/{n})")

        log.setdefault("stap1_klaar", []).append(oud)
        _save_log(log)


# ── stap 2: per-bericht routing (INBOX) ─────────────────────────────────────

def stap2_bronmappen_routing(mail, log: dict, dry: bool):
    print("\n── Stap 2: bronmappen per-bericht routing ──")
    bron_mappen = list_source_folders(mail)
    print(f"  {len(bron_mappen)} bronmappen")

    totaal_verplaatst = 0
    totaal_geen_match = 0
    al_verwerkt = set(log.get("stap2_verwerkt", []))

    for bron in bron_mappen:
        status, data = mail.select(quote_mailbox(bron), readonly=dry)
        if status != "OK":
            print(f"  Kan map niet openen: {bron}")
            continue

        n = int(data[0])
        if n == 0:
            continue
        print(f"  {bron}: {n} berichten")

        status, data = mail.uid("SEARCH", "ALL")
        if status != "OK" or not data or not data[0]:
            continue
        ids = [uid for uid in data[0].split() if uid]

        verplaatst = 0
        geen_match = 0

        for uid in ids:
            uid_str = uid.decode()
            log_key = f"{bron}:{uid_str}"
            if log_key in al_verwerkt:
                continue

            status, data = mail.uid("FETCH", uid, "(BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE)])")
            if status != "OK" or not data or not data[0]:
                continue

            msg = email.message_from_bytes(data[0][1])
            from_raw = decode_hdr(msg.get("From", ""))
            subject  = decode_hdr(msg.get("Subject", ""))
            date_str = msg.get("Date", "")

            doel = route_from_subject(from_raw, subject)

            if doel == "__FACTUUR_DATE__":
                doel = PREFIX + quarter_folder(date_str)
            elif doel:
                doel = PREFIX + doel
            else:
                geen_match += 1
                continue

            if bron == doel:
                continue

            if dry:
                print(f"    [DRY] {bron} → {doel}  |  {subject[:60]}")
            else:
                ok = move_message(mail, uid, bron, doel, dry=False)
                if ok:
                    verplaatst += 1
                    log.setdefault("stap2_verwerkt", []).append(log_key)
                    if verplaatst % 50 == 0:
                        mail.expunge()
                        _save_log(log)

        if not dry:
            mail.expunge()
            _save_log(log)

        totaal_verplaatst += verplaatst
        totaal_geen_match += geen_match
        print(f"    Verplaatst: {verplaatst}  |  Geen match: {geen_match}")

    print(f"  Totaal verplaatst: {totaal_verplaatst}  |  Totaal geen match: {totaal_geen_match}")


# ── stap 3: facturen per kwartaal ────────────────────────────────────────────

def stap3_facturen(mail, log: dict, dry: bool):
    print("\n── Stap 3: facturen per kwartaal ──")
    bron_mappen = [
        "INBOX.Facturen - te verwerken",
        "INBOX.Financieel.Facturen",   # als ze er al in zitten
    ]

    for bron in bron_mappen:
        status, data = mail.select(quote_mailbox(bron), readonly=dry)
        if status != "OK":
            continue

        n = int(data[0])
        if n == 0:
            print(f"  {bron}: leeg")
            continue

        print(f"  {bron}: {n} berichten")
        status, data = mail.uid("SEARCH", "ALL")
        if status != "OK" or not data or not data[0]:
            continue
        ids = [uid for uid in data[0].split() if uid]
        verplaatst = 0

        for uid in ids:
            status, data = mail.uid("FETCH", uid, "(BODY.PEEK[HEADER.FIELDS (DATE SUBJECT)])")
            if status != "OK" or not data or not data[0]:
                continue

            msg      = email.message_from_bytes(data[0][1])
            date_str = msg.get("Date", "")
            doel     = PREFIX + quarter_folder(date_str)

            if dry:
                dt_kort = date_str[:16] if date_str else "?"
                print(f"    [DRY] {dt_kort} → {doel}")
            else:
                ok = move_message(mail, uid, bron, doel, dry=False)
                if ok:
                    verplaatst += 1

        if not dry:
            mail.expunge()
            print(f"    {verplaatst}/{n} verplaatst")


# ── log ──────────────────────────────────────────────────────────────────────

def _save_log(log: dict):
    LOG_FILE.write_text(json.dumps(log, indent=2, ensure_ascii=False), encoding="utf-8")


def load_log() -> dict:
    if LOG_FILE.exists():
        try:
            return json.loads(LOG_FILE.read_text(encoding="utf-8"))
        except Exception:
            pass
    return {}


# ── main ──────────────────────────────────────────────────────────────────────

def run():
    if DRY_RUN:
        print("DRY RUN — er wordt niets verplaatst.")
        print("Voeg --uitvoeren toe om echt uit te voeren.\n")
    else:
        print("LET OP: dit verplaatst berichten op de echte server.")
        bevestig = input("Typ JA om door te gaan: ").strip()
        if bevestig != "JA":
            print("Afgebroken.")
            sys.exit(0)

    cfg = config.load()
    print(f"Account: {cfg.username}\n")

    print(f"Verbinden met {cfg.imap_host}:{cfg.imap_port} ...")
    try:
        mail = imaplib.IMAP4_SSL(cfg.imap_host, cfg.imap_port)
        mail.login(cfg.username, cfg.password)
    except Exception as e:
        print(f"Verbinding mislukt: {e}")
        sys.exit(1)

    log = load_log()

    stap1_mapverplaatsingen(mail, log, DRY_RUN)
    stap2_bronmappen_routing(mail, log, DRY_RUN)
    stap3_facturen(mail, log, DRY_RUN)

    mail.logout()
    print("\nKlaar.")
    if DRY_RUN:
        print("Draai met --uitvoeren om de wijzigingen door te voeren.")


if __name__ == "__main__":
    run()
