"""Email parsing and validation utilities for Mail and Packages."""

import email
import logging
import re
from typing import Any

from aioimaplib import IMAP4_SSL
from voluptuous import Email, MultipleInvalid, Schema

from custom_components.mail_and_packages.const import SENSOR_DATA
from custom_components.mail_and_packages.utils.cache import EmailCache

from .imap import email_fetch

_LOGGER = logging.getLogger(__name__)


def validate_email_address(email_address: str) -> bool:
    """Validate the format of an email address.

    Args:
        email_address (str): The email address to validate.

    Returns:
        bool: `True` if the email address is valid, `False` otherwise.

    """
    try:
        schema = Schema(Email())  # pylint: disable=no-value-for-parameter
        schema(email_address)
    except MultipleInvalid:
        _LOGGER.error("'%s' does not look like a valid email address", email_address)
        return False

    _LOGGER.debug("%s is a valid email address", email_address)

    return True


def generate_service_email_domains(amazon_fwds: list) -> set[str]:
    """Generate a set of service email domains from amazon domains and SENSOR_DATA.

    Returns:
        set[str]: A set of unique email domains.

    """
    domains = {fwd.split("@")[1] for fwd in amazon_fwds if "@" in fwd}
    for sensor in SENSOR_DATA.values():
        for address in sensor.get("email", []):
            if "@" not in address:
                continue
            domains.add(address.split("@")[1])
    return domains


def _match_patterns(
    text: str,
    patterns: list[re.Pattern],
    body_count: bool,
) -> tuple[int, int | None]:
    """Apply patterns to text and return occurrence count and extracted value.

    Returns:
        tuple[int, int | None]: (count_of_matches, extracted_value)

    """
    local_count = 0
    extracted_value = None

    for pattern in patterns:
        if body_count:
            if (found := pattern.search(text)) and len(found.groups()) > 0:
                _LOGGER.debug(
                    "Found (%s) in email result: %s",
                    pattern.pattern,
                    found.groups(),
                )
                extracted_value = int(found.group(1))
        elif (found := pattern.findall(text)) and len(found) > 0:
            _LOGGER.debug(
                "Found (%s) in email %s times.",
                pattern.pattern,
                len(found),
            )
            local_count += len(found)

    return local_count, extracted_value


async def _scan_email_for_text(
    account: type[IMAP4_SSL],
    email_id: str,
    patterns: list[re.Pattern],
    body_count: bool,
    cache: EmailCache | None = None,
) -> tuple[int, int | None]:
    """Scan a single email for terms.

    Returns:
        tuple[int, int | None]: (total_matches, last_extracted_value)

    """
    total_matches = 0
    last_value = None

    if cache:
        data = (await cache.fetch(email_id, "(RFC822)"))[1]
    else:
        data = (await email_fetch(account, email_id, "(RFC822)"))[1]
    for response_part in data:
        if not isinstance(response_part, (bytes, bytearray)):
            continue

        msg = email.message_from_bytes(response_part)

        for part in msg.walk():
            if part.get_content_type() not in ["text/html", "text/plain"]:
                continue

            email_msg = part.get_payload(decode=True)
            try:
                email_msg = email_msg.decode("utf-8", "ignore")
            except (AttributeError, UnicodeError):
                continue

            matches, value = _match_patterns(email_msg, patterns, body_count)
            total_matches += matches
            if value is not None:
                last_value = value

    return total_matches, last_value


async def find_text(
    sdata: Any,
    account: type[IMAP4_SSL],
    search_terms: list,
    body_count: bool,
    cache: EmailCache | None = None,
) -> int:
    """Filter for specific words in email."""
    _LOGGER.debug("Searching for (%s) in (%s) emails", search_terms, len(sdata))
    mail_list = sdata[0].split()
    count = 0

    # Pre-compile regex patterns once
    patterns = [re.compile(rf"{term}") for term in search_terms]

    for i in mail_list:
        matches, value = await _scan_email_for_text(
            account, i, patterns, body_count, cache
        )

        if body_count:
            # If extracting a value, "last found value wins" (updates count)
            if value is not None:
                count = value
        else:
            # If counting occurrences, accumulate
            count += matches

    return count


async def find_text_matches(
    sdata: Any,
    account: type[IMAP4_SSL],
    search_terms: list,
    body_count: bool,
    cache: EmailCache | None = None,
) -> tuple[int, list[bytes]]:
    """Filter for specific words in email and return matches and matching IDs."""
    _LOGGER.debug("Searching for (%s) in (%s) emails", search_terms, len(sdata))
    mail_list = sdata[0].split()
    count = 0
    matching_ids = []

    # Pre-compile regex patterns once
    patterns = [re.compile(rf"{term}") for term in search_terms]

    for i in mail_list:
        matches, value = await _scan_email_for_text(
            account, i, patterns, body_count, cache
        )

        matched = False
        if body_count:
            # If extracting a value, "last found value wins" (updates count)
            if value is not None:
                count = value
                matched = True
        # If counting occurrences, accumulate
        elif matches > 0:
            count += matches
            matched = True

        if matched:
            matching_ids.append(i)

    return count, matching_ids
