"""
Utility functions for proton-mail-bridge-client.

Helper functions for IMAP response parsing, email header decoding,
date/time conversion, and encoding handling.
"""

import email.header
import re
from datetime import datetime
from email.utils import parsedate_to_datetime

# Pre-compiled regex patterns for performance
_EMAIL_BRACKET_RE = re.compile(r"<([^>]+)>")
_MULTI_SLASH_RE = re.compile(r"/+")


def _decode_bytes_safe(data: bytes, fallback: str = "replace") -> str:
    """Decode bytes trying UTF-8 first, then latin-1 with error handling."""
    try:
        return data.decode("utf-8")
    except UnicodeDecodeError:
        return data.decode("latin-1", errors=fallback)


def parse_imap_list_response(response_line: bytes) -> tuple[str, str, str] | None:
    """
    Parse a single IMAP LIST response line.

    Args:
        response_line: Raw IMAP LIST response (e.g., b'(\\HasNoChildren) "/" "INBOX"')

    Returns:
        Tuple of (flags, delimiter, folder_name) or None if parsing fails

    Example:
        >>> parse_imap_list_response(b'(\\HasNoChildren) "/" "INBOX"')
        ('\\HasNoChildren', '/', 'INBOX')
    """
    try:
        response_str = response_line.decode("utf-8")

        # Pattern: (flags) "delimiter" "folder_name"
        # flags can be empty ()
        match = re.match(r'\(([^)]*)\)\s+"([^"]*)"\s+"?([^"]*)"?', response_str)
        if match:
            flags = match.group(1)
            delimiter = match.group(2)
            folder_name = match.group(3)
            return (flags, delimiter, folder_name)

        # Alternative pattern without quotes around folder name
        match = re.match(r'\(([^)]*)\)\s+"([^"]*)"\s+(\S+)', response_str)
        if match:
            flags = match.group(1)
            delimiter = match.group(2)
            folder_name = match.group(3)
            return (flags, delimiter, folder_name)

    except Exception:  # nosec B110 - resilience: return None for unparseable server responses
        pass

    return None


def decode_header(header_value: str) -> str:
    """
    Decode email header that may be RFC 2047 encoded.

    Handles headers like "=?UTF-8?B?VGVzdA==?=" and returns plain text.

    Args:
        header_value: Raw header value

    Returns:
        Decoded header string

    Example:
        >>> decode_header("=?UTF-8?B?SGVsbG8gV29ybGQ=?=")
        'Hello World'
    """
    if not header_value:
        return ""

    try:
        decoded_parts = email.header.decode_header(header_value)
        result_parts = []

        for content, charset in decoded_parts:
            if isinstance(content, bytes):
                if charset:
                    try:
                        result_parts.append(content.decode(charset))
                    except (UnicodeDecodeError, LookupError):
                        # Specified charset failed, use fallback decoding
                        result_parts.append(_decode_bytes_safe(content))
                else:
                    # No charset specified, use fallback decoding
                    result_parts.append(_decode_bytes_safe(content))
            else:
                result_parts.append(content)

        return "".join(result_parts)
    except Exception:
        # If decoding fails, return original
        return header_value


def parse_email_date(date_str: str) -> datetime | None:
    """
    Parse email date string to datetime object.

    Handles various email date formats per RFC 2822/5322.

    Args:
        date_str: Email date string

    Returns:
        datetime object or None if parsing fails

    Example:
        >>> parse_email_date("Mon, 06 Jan 2026 10:00:00 +0000")
        datetime.datetime(2026, 1, 6, 10, 0, tzinfo=...)
    """
    if not date_str:
        return None

    try:
        return parsedate_to_datetime(date_str)
    except Exception:
        return None


def parse_address_list(address_str: str) -> list[str]:
    """
    Parse comma-separated email addresses.

    Args:
        address_str: Comma-separated addresses (e.g., "user1@example.com, user2@example.com")

    Returns:
        List of email addresses

    Example:
        >>> parse_address_list("Alice <alice@example.com>, bob@example.com")
        ['alice@example.com', 'bob@example.com']
    """
    if not address_str:
        return []

    # Split by comma
    addresses = []
    for addr in address_str.split(","):
        addr = addr.strip()

        # Extract email from "Name <email>" format
        match = _EMAIL_BRACKET_RE.search(addr)
        if match:
            addresses.append(match.group(1))
        elif "@" in addr:
            # Plain email address
            addresses.append(addr)

    return addresses


def extract_email_from_address(address: str) -> str:
    """
    Extract just the email address from "Name <email>" format.

    Args:
        address: Email address with optional name

    Returns:
        Just the email address

    Example:
        >>> extract_email_from_address("Alice <alice@example.com>")
        'alice@example.com'
        >>> extract_email_from_address("bob@example.com")
        'bob@example.com'
    """
    if not address:
        return ""

    # Check for "Name <email>" format
    match = _EMAIL_BRACKET_RE.search(address)
    if match:
        return match.group(1)

    # Return as-is if no angle brackets
    return address.strip()


def is_system_folder(folder_name: str) -> bool:
    """
    Determine if a folder is a system folder.

    System folders are: INBOX, Sent, Drafts, Archive, Spam, Trash, All Mail
    Also includes "Folders" and "Labels" containers which are internal structure.

    Args:
        folder_name: Folder name to check

    Returns:
        True if system folder, False otherwise
    """
    system_folders = {
        "INBOX",
        "Sent",
        "Drafts",
        "Archive",
        "Spam",
        "Trash",
        "All Mail",
        "Sent Messages",
        "Deleted Messages",
        "Junk",
        "Folders",  # Container for custom folders
        "Labels",  # Container for labels
        "Starred",  # Added from ProtonMail documentation
    }

    return folder_name in system_folders


def is_custom_folder(folder_name: str) -> bool:
    """
    Check if a folder is a custom folder (subfolder of "Folders").

    Args:
        folder_name: Folder name to check

    Returns:
        True if custom folder, False otherwise

    Example:
        >>> is_custom_folder("Folders/MyFolder")
        True
        >>> is_custom_folder("INBOX")
        False
    """
    return folder_name.startswith("Folders/")


def is_label_folder(folder_name: str) -> bool:
    """
    Check if a folder is a label (subfolder of "Labels").

    Args:
        folder_name: Folder name to check

    Returns:
        True if label folder, False otherwise

    Example:
        >>> is_label_folder("Labels/MyLabel")
        True
        >>> is_label_folder("INBOX")
        False
    """
    return folder_name.startswith("Labels/")


def strip_folder_prefix(folder_name: str) -> str:
    """
    Remove "Folders/" prefix from a folder name.

    Args:
        folder_name: Folder name with potential prefix

    Returns:
        Folder name without "Folders/" prefix

    Example:
        >>> strip_folder_prefix("Folders/MyFolder")
        'MyFolder'
        >>> strip_folder_prefix("INBOX")
        'INBOX'
    """
    return folder_name.removeprefix("Folders/")


def strip_label_prefix(folder_name: str) -> str:
    """
    Remove "Labels/" prefix from a label name.

    Args:
        folder_name: Label name with potential prefix

    Returns:
        Label name without "Labels/" prefix

    Example:
        >>> strip_label_prefix("Labels/MyLabel")
        'MyLabel'
        >>> strip_label_prefix("Important")
        'Important'
    """
    return folder_name.removeprefix("Labels/")


def safe_decode_bytes(data: bytes, fallback: str = "replace") -> str:
    """
    Safely decode bytes to string, trying multiple encodings.

    Tries UTF-8 first, then latin-1 with error handling.

    Args:
        data: Bytes to decode
        fallback: Error handling strategy ('replace', 'ignore', 'strict')

    Returns:
        Decoded string
    """
    if not data:
        return ""
    return _decode_bytes_safe(data, fallback)


def parse_imap_address(address_structure: str) -> str:
    """
    Parse IMAP address structure to email address string.

    IMAP address format: (("name" NIL "mailbox" "host"))
    or multiple addresses: (("name1" NIL "mailbox1" "host1") ("name2" NIL "mailbox2" "host2"))
    or NIL for empty

    Args:
        address_structure: IMAP address structure string

    Returns:
        Formatted email address (e.g., "Name <email@host>" or "email@host")
        Returns empty string if parsing fails or structure is NIL

    Example:
        >>> parse_imap_address('(("John Doe" NIL "john" "example.com"))')
        'John Doe <john@example.com>'
        >>> parse_imap_address('((NIL NIL "john" "example.com"))')
        'john@example.com'
    """
    if not address_structure or address_structure.strip() == "NIL":
        return ""

    try:
        # Extract first address from structure
        # Format: (("name" NIL "mailbox" "host"))
        # We want to extract: name, mailbox, host

        # Remove outer parentheses
        addr_str = address_structure.strip()
        if addr_str.startswith("(("):
            addr_str = addr_str[2:]
        if addr_str.endswith("))"):
            addr_str = addr_str[:-2]

        # Parse quoted strings and atoms
        # Pattern: "name" NIL "mailbox" "host" or NIL NIL "mailbox" "host"
        parts = []
        current = ""
        in_quotes = False
        i = 0

        while i < len(addr_str):
            char = addr_str[i]

            if char == '"' and (i == 0 or addr_str[i - 1] != "\\"):
                if in_quotes:
                    # End of quoted string
                    parts.append(current)
                    current = ""
                    in_quotes = False
                else:
                    # Start of quoted string
                    in_quotes = True
                i += 1
                continue

            if in_quotes:
                # Inside quotes, collect everything
                current += char
                i += 1
                continue

            # Outside quotes
            if char == " ":
                if current:
                    parts.append(current)
                    current = ""
                i += 1
                continue

            current += char
            i += 1

        # Add last part
        if current:
            parts.append(current)

        # Now we should have 4 parts: [name, NIL, mailbox, host]
        if len(parts) >= 4:
            name = parts[0] if parts[0] != "NIL" else ""
            mailbox = parts[2] if parts[2] != "NIL" else ""
            host = parts[3] if parts[3] != "NIL" else ""

            if mailbox and host:
                email_addr = f"{mailbox}@{host}"
                if name:
                    return f"{name} <{email_addr}>"
                return email_addr

        return ""

    except Exception:
        return ""


def parse_imap_envelope(envelope_str: str) -> dict[str, str | None]:
    """
    Parse IMAP ENVELOPE structure into a dictionary.

    ENVELOPE format (RFC 3501):
    (date subject from sender reply-to to cc bcc in-reply-to message-id)

    Args:
        envelope_str: ENVELOPE structure string from IMAP FETCH

    Returns:
        Dictionary with keys: date, subject, from, sender, reply_to, to, cc, bcc,
        in_reply_to, message_id. Values are strings or None.

    Example:
        >>> envelope = '("Mon, 06 Jan 2026 10:00:00 +0000" "Test Subject" ...)'
        >>> result = parse_imap_envelope(envelope)
        >>> result['subject']
        'Test Subject'
    """
    result: dict[str, str | None] = {
        "date": None,
        "subject": None,
        "from": None,
        "sender": None,
        "reply_to": None,
        "to": None,
        "cc": None,
        "bcc": None,
        "in_reply_to": None,
        "message_id": None,
    }

    if not envelope_str:
        return result

    try:
        # Remove outer parentheses from ENVELOPE structure
        env = envelope_str.strip()
        if env.startswith("("):
            env = env[1:]
        if env.endswith(")"):
            env = env[:-1]

        # Parse ENVELOPE fields
        # We need to handle quoted strings and nested parentheses
        fields = []
        current = ""
        depth = 0
        in_quotes = False
        i = 0

        while i < len(env):
            char = env[i]

            # Handle escape sequences in quotes
            if char == "\\" and in_quotes and i + 1 < len(env):
                current += char + env[i + 1]
                i += 2
                continue

            # Handle quotes
            if char == '"':
                in_quotes = not in_quotes
                current += char
                i += 1
                continue

            # Handle parentheses (for nested structures like addresses)
            if not in_quotes:
                if char == "(":
                    depth += 1
                    current += char
                    i += 1
                    continue
                elif char == ")":
                    depth -= 1
                    current += char
                    i += 1
                    continue

                # Space separates fields (only at depth 0)
                if char == " " and depth == 0:
                    if current.strip():
                        fields.append(current.strip())
                        current = ""
                    i += 1
                    continue

            current += char
            i += 1

        # Add last field
        if current.strip():
            fields.append(current.strip())

        # Extract fields (ENVELOPE has exactly 10 fields)
        field_names = [
            "date",
            "subject",
            "from",
            "sender",
            "reply_to",
            "to",
            "cc",
            "bcc",
            "in_reply_to",
            "message_id",
        ]

        for idx, field_name in enumerate(field_names):
            if idx < len(fields):
                field_value = fields[idx]

                # Remove quotes from simple strings
                if field_value.startswith('"') and field_value.endswith('"'):
                    field_value = field_value[1:-1]

                # Convert NIL to None
                if field_value == "NIL":
                    result[field_name] = None
                # Parse addresses (fields with parentheses)
                elif field_name in ("from", "sender", "reply_to", "to", "cc", "bcc"):
                    result[field_name] = parse_imap_address(field_value)
                else:
                    result[field_name] = field_value

        return result

    except Exception:
        return result


def normalize_folder_path(name: str) -> str:
    """
    Normalize a folder path by cleaning up common issues.

    - Strips leading/trailing whitespace and slashes
    - Collapses double slashes to single slashes
    - Returns empty string if name is only whitespace/slashes

    Args:
        name: Folder name or path to normalize

    Returns:
        Normalized folder path

    Example:
        >>> normalize_folder_path("  foo/bar  ")
        'foo/bar'
        >>> normalize_folder_path("foo//bar")
        'foo/bar'
        >>> normalize_folder_path("/foo/bar/")
        'foo/bar'
        >>> normalize_folder_path("///")
        ''
    """
    if not name:
        return ""

    # Strip whitespace
    result = name.strip()

    # Strip leading/trailing slashes
    result = result.strip("/")

    # Collapse multiple slashes to single (single-pass via regex)
    result = _MULTI_SLASH_RE.sub("/", result)

    return result


def split_folder_path(name: str) -> list[str]:
    """
    Split a folder path into its component parts.

    Normalizes the path first, then splits on '/'.

    Args:
        name: Folder name or path to split

    Returns:
        List of path components (empty list if name is empty/invalid)

    Example:
        >>> split_folder_path("foo/bar/baz")
        ['foo', 'bar', 'baz']
        >>> split_folder_path("single")
        ['single']
        >>> split_folder_path("")
        []
        >>> split_folder_path("foo//bar")
        ['foo', 'bar']
    """
    normalized = normalize_folder_path(name)
    if not normalized:
        return []
    return normalized.split("/")
