"""Amazon specific utilities."""

from __future__ import annotations

import datetime
import email
import logging
import re
from email.header import decode_header
from functools import partial
from pathlib import Path
from typing import Any

import aiohttp
import dateparser
from aioimaplib import IMAP4_SSL

from custom_components.mail_and_packages.const import (
    AMAZON_DELIVERED_SUBJECT,
    AMAZON_DOMAINS,
    AMAZON_EMAIL,
    AMAZON_IMG_LIST,
    AMAZON_IMG_PATTERN,
    AMAZON_ORDERED_SUBJECT,
    AMAZON_SHIPMENT_SUBJECT,
    AMAZON_SHIPMENT_TRACKING,
    AMAZON_TIME_PATTERN,
    AMAZON_TIME_PATTERN_END,
    AMAZON_TIME_PATTERN_REGEX,
    DEFAULT_AMAZON_DAYS,
)
from custom_components.mail_and_packages.utils.cache import EmailCache
from custom_components.mail_and_packages.utils.date import get_today
from custom_components.mail_and_packages.utils.image import io_save_file
from custom_components.mail_and_packages.utils.imap import (
    email_fetch,
    email_search,
)

_LOGGER = logging.getLogger(__name__)

_MAX_IMAGE_SIZE = 10 * 1024 * 1024  # 10 MB

DOMAIN_LANG_MAP = {
    "amazon.de": [
        "versandbestaetigung",
        "Geliefert:",
        "Zugestellt:",
        "Versandt:",
        "In Zustellung:",
        "Zustellung:",
    ],
    "amazon.it": [
        "conferma-spedizione",
        "Consegna effettuata:",
        "Arriverà",
        "Spedito:",
    ],
    "amazon.nl": [
        "update-bestelling",
        "verzending-volgen",
        "auto-bevestiging",
        "Bezorgd:",
    ],
    "amazon.fr": ["confirmation-commande", "Livré", "Livraison : Votre", "Arrivée :"],
    "amazon.ca": ["confirmation-commande", "Livré", "Livraison : Votre", "Arrivée :"],
    "amazon.es": [
        "confirmar-envio",
        "Entregado:",
        "Enviado:",
        "Pedido efetuado:",
        "Chega ",
    ],
    "amazon.pl": ["Dostarczono:"],
}


def filter_amazon_strings(strings: list[str], domain: str) -> list[str]:
    """Filter list of strings based on the domain language."""
    all_mapped_strings = []
    for lang_list in DOMAIN_LANG_MAP.values():
        all_mapped_strings.extend(lang_list)

    def is_mapped(s: str) -> bool:
        return any(m in s for m in all_mapped_strings)

    base_strings = [s for s in strings if not is_mapped(s)]

    domain_strings = []
    if domain in DOMAIN_LANG_MAP:
        mapped_for_domain = DOMAIN_LANG_MAP[domain]
        domain_strings = [s for s in strings if any(m in s for m in mapped_for_domain)]

    # Only amazon.ca and unmapped domains (like .com, .co.uk) should use base English strings
    if domain in DOMAIN_LANG_MAP and domain != "amazon.ca":
        return list(dict.fromkeys(domain_strings))

    return list(dict.fromkeys(base_strings + domain_strings))


def get_decoded_subject(msg: email.message.Message) -> str:
    """Decode email subject."""
    header_val = msg["subject"]
    if header_val:
        decoded_parts = []
        for subject_bytes, encoding in decode_header(header_val):
            if isinstance(subject_bytes, bytes):
                if encoding:
                    try:
                        decoded_parts.append(subject_bytes.decode(encoding, "ignore"))
                        continue
                    except (LookupError, UnicodeError):
                        pass
                decoded_parts.append(subject_bytes.decode("utf-8", "ignore"))
            else:
                decoded_parts.append(str(subject_bytes))
        return "".join(decoded_parts)

    body = get_email_body(msg)
    if not body:
        return ""
    title_match = re.search(
        r"<title[^>]*>([^<]+)</title>", body, re.IGNORECASE | re.DOTALL
    )
    if not title_match:
        return ""
    return title_match.group(1).strip()


def get_email_body(msg: email.message.Message) -> str:
    """Extract and decode the email body safely."""
    try:
        if msg.is_multipart():
            # Standard practice is to look for text/plain then text/html
            for part in msg.walk():
                if part.get_content_type() == "text/plain":
                    return part.get_payload(decode=True).decode("utf-8", "ignore")
            # If no text/plain, fall back to first part
            payload = msg.get_payload(0)
            if isinstance(payload, email.message.Message):
                return payload.get_payload(decode=True).decode("utf-8", "ignore")
            return str(payload)

        return msg.get_payload(decode=True).decode("utf-8", "ignore")
    except (ValueError, TypeError, IndexError, AttributeError) as err:
        _LOGGER.debug("Problem decoding email message: %s", err)
        return ""


def extract_order_numbers(text: str, pattern: re.Pattern | str) -> list[str]:
    """Extract order numbers from text."""
    if isinstance(pattern, str):
        pattern = re.compile(pattern)
    return pattern.findall(text)


async def parse_amazon_arrival_date(
    hass: Any,
    email_msg: str,
    email_date: datetime.date,
) -> datetime.date | None:
    """Determine arrival date from email."""
    today_date = get_today()

    # Try using regex for more precise extraction of the arrival date string
    if date_str := amazon_date_regex(email_msg):
        base_datetime = datetime.datetime.combine(
            email_date or today_date,
            datetime.time(),
        )

        # 1. Try parsing without PREFER_DATES_FROM: future to handle relative terms (any language)
        dateobj = await hass.async_add_executor_job(
            partial(
                dateparser.parse,
                date_str,
                settings={
                    "RELATIVE_BASE": base_datetime,
                    "RETURN_AS_TIMEZONE_AWARE": False,
                },
            ),
        )
        if dateobj:
            parsed_date = dateobj.date()
            base_date = email_date or today_date
            # Only accept matches that resolve to email_date (today) or email_date + 1 day (tomorrow)
            if (
                parsed_date == base_date
                or parsed_date == base_date + datetime.timedelta(days=1)
            ):
                return parsed_date

        # 2. Fall back to parsing with PREFER_DATES_FROM: future for absolute dates
        dateobj = await hass.async_add_executor_job(
            partial(
                dateparser.parse,
                date_str,
                settings={
                    "PREFER_DATES_FROM": "future",
                    "RELATIVE_BASE": base_datetime,
                    "RETURN_AS_TIMEZONE_AWARE": False,
                },
            ),
        )
        if dateobj:
            return dateobj.date()

    # Fallback to chunk-based parsing
    for search in AMAZON_TIME_PATTERN:
        if search not in email_msg:
            continue

        start = email_msg.find(search) + len(search)
        chunk = email_msg[start : start + 50]

        dateobj = await hass.async_add_executor_job(
            partial(
                dateparser.parse,
                chunk,
                settings={
                    "PREFER_DATES_FROM": "future",
                    "RELATIVE_BASE": datetime.datetime.combine(
                        email_date or today_date,
                        datetime.time(),
                    ),
                    "RETURN_AS_TIMEZONE_AWARE": False,
                },
            ),
        )
        if dateobj:
            return dateobj.date()
    return None


def amazon_email_addresses(
    fwds: list[str] | str | None = None,
    domain: str | None = None,
) -> list[str]:
    """Generate Amazon email addresses."""
    if isinstance(fwds, str):
        fwds = [fwds]
    elif not isinstance(fwds, (list, tuple)):
        fwds = None

    if domain is None:
        domain = "amazon.com"

    # Use both AMAZON_EMAIL and AMAZON_SHIPMENT_TRACKING for prefixes
    prefixes = list(AMAZON_EMAIL)
    for p in AMAZON_SHIPMENT_TRACKING:
        if f"{p}@" not in prefixes:
            prefixes.append(f"{p}@")

    prefixes = filter_amazon_strings(prefixes, domain)

    value = [f"{e}{domain}" for e in prefixes]
    if fwds:
        for fwd in fwds:
            if "@" in fwd:
                value.append(fwd)
            elif any(f in fwd for f in AMAZON_DOMAINS):
                value.extend(f"{e}{fwd}" for e in prefixes)
    return value


async def search_amazon_emails(
    account: IMAP4_SSL,
    address_list: list[str],
    days: int,
    domain: str | None = None,
    cache: EmailCache | None = None,
    forwarding_header: str = "",
) -> list[bytes]:
    """Search for Amazon emails."""
    if not isinstance(days, int):
        try:
            days = int(days)
        except (ValueError, TypeError):
            days = DEFAULT_AMAZON_DAYS

    past_date = get_today() - datetime.timedelta(days=days)
    tfmt = past_date.strftime("%d-%b-%Y")
    amazon_subjects = (
        AMAZON_DELIVERED_SUBJECT + AMAZON_SHIPMENT_SUBJECT + AMAZON_ORDERED_SUBJECT
    )
    if domain:
        amazon_subjects = filter_amazon_strings(amazon_subjects, domain)

    (server_response, sdata) = await email_search(
        account=account,
        address=address_list,
        date=tfmt,
        subject=amazon_subjects,
        header=forwarding_header,
    )

    if server_response != "OK" or not sdata[0]:
        return []

    return sdata[0].split()


async def download_amazon_img(
    img_url: str,
    img_path: str,
    img_name: str,
    hass: Any,
) -> None:
    """Download image from url."""
    img_path = Path(img_path) / "amazon"
    filepath = img_path / img_name
    timeout = aiohttp.ClientTimeout(total=30)
    async with aiohttp.ClientSession(timeout=timeout) as session:
        try:
            async with session.get(img_url.replace("&amp;", "&")) as resp:
                if resp.status != 200:
                    return
                content_type = resp.headers.get("content-type", "")
                if "image" not in content_type:
                    return
                content_length = int(resp.headers.get("content-length", 0))
                if content_length > _MAX_IMAGE_SIZE:
                    _LOGGER.warning(
                        "Amazon image too large to download (%d bytes), skipping",
                        content_length,
                    )
                    return
                data = await resp.read()
                if len(data) > _MAX_IMAGE_SIZE:
                    _LOGGER.warning(
                        "Amazon image exceeds size limit after download, discarding"
                    )
                    return
                await hass.async_add_executor_job(io_save_file, filepath, data)
        except aiohttp.ClientError as err:
            _LOGGER.error("Problem downloading file: %s", err)


async def get_amazon_image_urls(
    sdata: Any,
    account: IMAP4_SSL,
    cache: EmailCache | None = None,
) -> list[str]:
    """Find all Amazon delivery image URLs."""
    mail_list = sdata.split()
    pattern = re.compile(rf"{AMAZON_IMG_PATTERN}")
    urls = []
    for i in mail_list:
        if cache:
            data = (await cache.fetch(i, "(RFC822)"))[1]
        else:
            data = (await email_fetch(account, i, "(RFC822)"))[1]
        for response_part in data:
            if isinstance(response_part, (bytes, bytearray)):
                msg = email.message_from_bytes(response_part)
                for part in msg.walk():
                    if part.get_content_type() != "text/html":
                        continue
                    part_payload = part.get_payload(decode=True)
                    part_content = part_payload.decode("utf-8", "ignore")
                    found = pattern.findall(part_content)
                    for url in found:
                        if url[1] not in AMAZON_IMG_LIST:
                            continue
                        full_url = url[0] + url[1] + url[2]
                        if full_url not in urls:
                            urls.append(full_url)
    return urls


def _extract_hub_code(
    body: str,
    hub_pattern: str,
    subject: str,
    subject_pattern: str,
) -> str:
    """Extract Amazon Hub code from email body or subject."""
    # Check subject first
    if (found := re.compile(subject_pattern).search(subject)) is not None:
        return found.group(3)

    # Check body
    if (found := re.compile(hub_pattern).search(body)) is not None:
        return found.group(2)
    return ""


def amazon_date_search(email_msg: str, patterns: list[str] | None = None) -> int:
    """Search for a date pattern in an email message and return its index."""
    if patterns is None:
        patterns = AMAZON_TIME_PATTERN_END

    for pattern in patterns:
        if (index := email_msg.find(pattern)) != -1:
            return index
    return -1


def amazon_date_regex(email_msg: str, patterns: list[str] | None = None) -> str | None:
    """Search for a date pattern using regex and return the first capture group."""
    if patterns is None:
        patterns = AMAZON_TIME_PATTERN_REGEX

    for pattern in patterns:
        if (found := re.compile(pattern, re.IGNORECASE).search(email_msg)) is not None:
            if found.groups():
                return found.group(1)
    return None
