"""Zimbra email operations."""

import json
import os
import re
import sys

from .api import soap, upload_attachment, download_attachment, get_username

MAIL_NS = "urn:zimbraMail"


def search(query, limit=20, offset=0):
    """Search emails. Returns dict with total and emails list."""
    result = soap(MAIL_NS, "SearchRequest", {
        "query": query,
        "types": "message",
        "sortBy": "dateDesc",
        "limit": limit,
        "offset": offset,
    })
    resp = result.get("SearchResponse", {})
    messages = resp.get("m", [])
    total = resp.get("total", 0)

    emails = []
    for m in messages:
        addrs = m.get("e", [])
        from_addr = next((e.get("a", "") for e in addrs if e.get("t") == "f"), "")
        flags = m.get("f", "")
        emails.append({
            "id": m.get("id"),
            "subject": m.get("su", "(no subject)"),
            "from": from_addr,
            "date": m.get("d"),
            "fragment": m.get("fr", ""),
            "unread": "u" in flags,
            "flagged": "f" in flags,
            "has_attachment": bool(m.get("mp")) or "att" in flags,
            "folder": m.get("l"),
            "size": m.get("s"),
        })
    return {"total": total, "emails": emails}


def read(msg_id, download_dir=None):
    """Read an email by ID. Downloads attachments to download_dir if set."""
    result = soap(MAIL_NS, "GetMsgRequest", {
        "m": {"id": str(msg_id), "html": 0, "needExp": 1},
    })
    m = result.get("GetMsgResponse", {}).get("m", [{}])[0]

    addrs = m.get("e", [])
    from_addr = next((e.get("a", "") for e in addrs if e.get("t") == "f"), "")
    to_addrs = [e.get("a", "") for e in addrs if e.get("t") == "t"]
    cc_addrs = [e.get("a", "") for e in addrs if e.get("t") == "c"]

    body_parts = []
    attachments = []
    _walk_parts(m.get("mp", []), body_parts, attachments)
    body = "\n".join(body_parts) or m.get("fr", "")

    output = {
        "id": m.get("id"),
        "conversation_id": m.get("cid"),
        "subject": m.get("su", "(no subject)"),
        "from": from_addr,
        "to": to_addrs,
        "cc": cc_addrs,
        "date": m.get("d"),
        "body": body,
        "folder": m.get("l"),
    }

    if attachments:
        if download_dir:
            download_dir = os.path.expanduser(download_dir)
            os.makedirs(download_dir, exist_ok=True)
            for att in attachments:
                dest = os.path.join(download_dir, att["filename"])
                download_attachment(msg_id, att["part"], dest)
                att["local_path"] = dest
        output["attachments"] = attachments

    return output


def _walk_parts(parts, body_parts, attachments):
    """Recursively walk MIME parts."""
    for part in parts:
        ct = part.get("ct", "")
        cd = part.get("cd", "")
        filename = part.get("filename", "")

        if filename or cd == "attachment":
            attachments.append({
                "part": part.get("part"),
                "filename": filename or f"attachment-{part.get('part')}",
                "content_type": ct,
                "size": part.get("s", 0),
            })
        elif ct == "text/plain" and part.get("content"):
            body_parts.append(part["content"])
        elif ct == "text/html" and not body_parts and part.get("content"):
            text = re.sub(r"<[^>]+>", " ", part["content"])
            text = text.replace("&nbsp;", " ").replace("&amp;", "&")
            text = re.sub(r"\s+", " ", text).strip()
            body_parts.append(text)

        if part.get("mp"):
            _walk_parts(part["mp"], body_parts, attachments)


def send(to, subject, body, cc=None, bcc=None, html_body=None,
         attachments=None, reply_to_id=None):
    """Send an email. attachments is a list of file paths."""
    msg = {
        "e": [],
        "su": {"_content": subject},
    }

    for addr in (to if isinstance(to, list) else [to]):
        msg["e"].append({"t": "t", "a": addr})
    if cc:
        for addr in (cc if isinstance(cc, list) else [cc]):
            msg["e"].append({"t": "c", "a": addr})
    if bcc:
        for addr in (bcc if isinstance(bcc, list) else [bcc]):
            msg["e"].append({"t": "b", "a": addr})

    if html_body:
        msg["mp"] = {
            "ct": "multipart/alternative",
            "mp": [
                {"ct": "text/plain", "content": {"_content": body}},
                {"ct": "text/html", "content": {"_content": html_body}},
            ],
        }
    else:
        msg["mp"] = {"ct": "text/plain", "content": {"_content": body}}

    if attachments:
        aids = []
        for filepath in attachments:
            filepath = os.path.expanduser(filepath)
            if not os.path.exists(filepath):
                raise FileNotFoundError(f"Attachment not found: {filepath}")
            aids.append(upload_attachment(filepath))
        body_mp = msg["mp"]
        msg["mp"] = {"ct": "multipart/mixed", "mp": [body_mp]}
        msg["attach"] = {"aid": ",".join(aids)}

    if reply_to_id:
        msg["origid"] = str(reply_to_id)
        msg["rt"] = "r"

    result = soap(MAIL_NS, "SendMsgRequest", {"m": msg})
    resp = result.get("SendMsgResponse", {})
    sent_id = resp.get("m", [{}])[0].get("id", "")
    return {"success": True, "id": sent_id}


def reply(msg_id, body, reply_all=False, attachments=None):
    """Reply to an email."""
    result = soap(MAIL_NS, "GetMsgRequest", {
        "m": {"id": str(msg_id), "html": 0, "needExp": 1},
    })
    orig = result.get("GetMsgResponse", {}).get("m", [{}])[0]
    addrs = orig.get("e", [])
    from_addr = next((e.get("a", "") for e in addrs if e.get("t") == "f"), "")

    msg = {
        "e": [{"t": "t", "a": from_addr}],
        "su": {"_content": "Re: " + orig.get("su", "")},
        "mp": {"ct": "text/plain", "content": {"_content": body}},
        "origid": str(msg_id),
        "rt": "r",
    }

    if reply_all:
        my_addr = get_username().lower()
        for e in addrs:
            addr = e.get("a", "").lower()
            if addr != my_addr and addr != from_addr.lower() and e.get("t") in ("t", "c"):
                msg["e"].append({"t": "c", "a": e["a"]})

    if attachments:
        aids = []
        for filepath in attachments:
            filepath = os.path.expanduser(filepath)
            if not os.path.exists(filepath):
                raise FileNotFoundError(f"Attachment not found: {filepath}")
            aids.append(upload_attachment(filepath))
        body_mp = msg["mp"]
        msg["mp"] = {"ct": "multipart/mixed", "mp": [body_mp]}
        msg["attach"] = {"aid": ",".join(aids)}

    result = soap(MAIL_NS, "SendMsgRequest", {"m": msg})
    resp = result.get("SendMsgResponse", {})
    return {"success": True, "id": resp.get("m", [{}])[0].get("id", "")}


def forward(msg_id, to, body="", attachments=None):
    """Forward an email, including original attachments."""
    result = soap(MAIL_NS, "GetMsgRequest", {
        "m": {"id": str(msg_id), "html": 0},
    })
    orig = result.get("GetMsgResponse", {}).get("m", [{}])[0]

    subj = orig.get("su", "")
    if not subj.lower().startswith("fwd:"):
        subj = "Fwd: " + subj

    msg = {
        "e": [],
        "su": {"_content": subj},
        "mp": {"ct": "text/plain", "content": {"_content": body}},
        "origid": str(msg_id),
        "rt": "w",
    }

    for addr in (to if isinstance(to, list) else [to]):
        msg["e"].append({"t": "t", "a": addr})

    # Include original attachments
    orig_atts = []
    _collect_attachment_parts(orig.get("mp", []), orig_atts)
    if orig_atts:
        msg["attach"] = {"mp": [{"mid": str(msg_id), "part": a["part"]} for a in orig_atts]}

    if attachments:
        aids = []
        for filepath in attachments:
            filepath = os.path.expanduser(filepath)
            if not os.path.exists(filepath):
                raise FileNotFoundError(f"Attachment not found: {filepath}")
            aids.append(upload_attachment(filepath))
        if "attach" not in msg:
            msg["attach"] = {}
        msg["attach"]["aid"] = ",".join(aids)

    result = soap(MAIL_NS, "SendMsgRequest", {"m": msg})
    resp = result.get("SendMsgResponse", {})
    return {"success": True, "id": resp.get("m", [{}])[0].get("id", "")}


def _collect_attachment_parts(parts, attachments):
    for part in parts:
        if part.get("filename") or part.get("cd") == "attachment":
            attachments.append({"part": part.get("part"), "filename": part.get("filename", "")})
        if part.get("mp"):
            _collect_attachment_parts(part["mp"], attachments)


def folders():
    """List mail folders."""
    result = soap(MAIL_NS, "GetFolderRequest", {"folder": {"l": "1"}})
    root = result.get("GetFolderResponse", {}).get("folder", [{}])[0]
    output = []
    _walk_folders(root, output, "")
    return output


def _walk_folders(folder, output, prefix):
    name = folder.get("name", "")
    path = f"{prefix}/{name}" if prefix else name
    output.append({
        "id": folder.get("id"),
        "name": name,
        "path": path,
        "unread": folder.get("u", 0),
        "total": folder.get("n", 0),
        "view": folder.get("view", ""),
    })
    for child in folder.get("folder", []):
        _walk_folders(child, output, path)


def move(msg_ids, folder_id):
    """Move messages to a folder."""
    ids = ",".join(str(i) for i in msg_ids)
    soap(MAIL_NS, "MsgActionRequest", {"action": {"id": ids, "op": "move", "l": str(folder_id)}})
    return {"success": True, "moved": len(msg_ids)}


def delete(msg_ids, hard=False):
    """Delete messages."""
    ids = ",".join(str(i) for i in msg_ids)
    soap(MAIL_NS, "MsgActionRequest", {"action": {"id": ids, "op": "delete" if hard else "trash"}})
    return {"success": True, "deleted": len(msg_ids), "hard": hard}


def mark_read(msg_ids, read=True):
    """Mark messages read/unread."""
    ids = ",".join(str(i) for i in msg_ids)
    soap(MAIL_NS, "MsgActionRequest", {"action": {"id": ids, "op": "read" if read else "!read"}})
    return {"success": True, "count": len(msg_ids), "read": read}


def flag(msg_ids, flagged=True):
    """Flag/unflag messages."""
    ids = ",".join(str(i) for i in msg_ids)
    soap(MAIL_NS, "MsgActionRequest", {"action": {"id": ids, "op": "flag" if flagged else "!flag"}})
    return {"success": True, "count": len(msg_ids), "flagged": flagged}
