import logging
import os
from dataclasses import dataclass
from typing import Any, List, Optional, Sequence, Tuple, Union

import requests
from django.core.exceptions import ValidationError
from django.core.mail import EmailMultiAlternatives
from django.core.validators import validate_email
from django.utils.html import strip_tags

logger = logging.getLogger(__name__)

DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", "")
EMAIL_FROM = os.getenv("EMAIL_FROM") or DEFAULT_FROM_EMAIL
ENABLE_EMAIL_DELIVERY = (os.getenv("ENABLE_EMAIL_DELIVERY") or "").lower() == "true"
EMAIL_API_KEY = os.getenv("EMAIL_API_KEY", "")
EMAIL_DOMAIN = os.getenv("EMAIL_DOMAIN", "")
DEVELOPMENT_MODE = (os.getenv("DEVELOPMENT_MODE") or "").lower() == "true"
EMAIL_DELIVERY_PROVIDER = (os.getenv("EMAIL_DELIVERY_PROVIDER") or "").lower()
BCC_RECIPIENTS = os.getenv("BCC_RECIPIENTS", "")
EMAIL_REPLY_TO = os.getenv("EMAIL_REPLY_TO", "")
LOG_EMAIL_RESPONSE_TEXT = (os.getenv("EMAIL_LOG_RESPONSE_TEXT") or "").lower() == "true"


RecipientInput = Union[str, Sequence[str], None]
Attachment = Tuple[str, Any, str]
EmailDeliveryResponse = Optional[Union[requests.Response, "DeliveryResult"]]


@dataclass(frozen=True)
class DeliveryResult:
    status_code: int
    text: str
    backend: str


def _get_delivery_provider(enabled: Any = None) -> Optional[str]:
    if enabled is not None:
        if isinstance(enabled, str):
            normalized_enabled = enabled.strip().lower()
            if normalized_enabled in {"true", "1", "yes", "on"}:
                if EMAIL_DELIVERY_PROVIDER in {"mailgun", "mailpit", "smtp"}:
                    return EMAIL_DELIVERY_PROVIDER
                return "mailgun"
            if normalized_enabled in {"false", "0", "no", "off"}:
                return "disabled"
            logger.error("Invalid enabled override: %r", enabled)
            return None

        if isinstance(enabled, bool):
            if not enabled:
                return "disabled"
            if EMAIL_DELIVERY_PROVIDER in {"mailgun", "mailpit", "smtp"}:
                return EMAIL_DELIVERY_PROVIDER
            return "mailgun"

        logger.error("Invalid enabled override type: %s", type(enabled).__name__)
        return None

    if EMAIL_DELIVERY_PROVIDER in {"mailgun", "mailpit", "smtp", "disabled"}:
        return EMAIL_DELIVERY_PROVIDER
    if ENABLE_EMAIL_DELIVERY:
        return "mailgun"
    if DEVELOPMENT_MODE:
        return "smtp"
    return "disabled"


def _get_from_email() -> Optional[str]:
    return EMAIL_FROM or DEFAULT_FROM_EMAIL


def _has_mailgun_transport_config() -> bool:
    missing = []
    if not EMAIL_DOMAIN:
        missing.append("EMAIL_DOMAIN")
    if not EMAIL_API_KEY:
        missing.append("EMAIL_API_KEY")
    if not _get_from_email():
        missing.append("EMAIL_FROM or DEFAULT_FROM_EMAIL")

    if missing:
        logger.warning("Email not sent: missing Mailgun config %s", ", ".join(missing))
        return False

    return True


def _log_provider_response(*, provider: str, response: requests.Response) -> None:
    logger.info("Email provider response: provider=%s response_code=%s", provider, response.status_code)

    if LOG_EMAIL_RESPONSE_TEXT and logger.isEnabledFor(logging.DEBUG):
        preview = response.text[:500]
        logger.debug("Email provider response text: provider=%s text=%s", provider, preview)


def _build_bcc_recipients(bcc: RecipientInput) -> List[str]:
    static_bcc = _recipients_to_list(globals().get("BCC_RECIPIENTS", []))
    passed_bcc = _recipients_to_list(bcc)
    merged_bcc = _filter_valid_emails(static_bcc + passed_bcc, field_name="bcc")
    if not merged_bcc:
        return []

    seen = set()
    deduped_bcc: List[str] = []
    for recipient in merged_bcc:
        key = recipient.lower()
        if key in seen:
            continue
        seen.add(key)
        deduped_bcc.append(recipient)
    return deduped_bcc


def _build_reply_to(reply_to: RecipientInput) -> List[str]:
    configured_reply_to = _recipients_to_list(globals().get("EMAIL_REPLY_TO", ""))
    passed_reply_to = _recipients_to_list(reply_to)
    return _filter_valid_emails(configured_reply_to + passed_reply_to, field_name="reply_to")


def _send_via_mailgun(
    to: Union[str, Sequence[str]],
    subject: str,
    html: str,
    attachments: Optional[Sequence[Attachment]] = None,
    cc: RecipientInput = None,
    bcc: RecipientInput = None,
    reply_to: RecipientInput = None,
) -> Optional[requests.Response]:
    if not _has_mailgun_transport_config():
        return None

    url = f"https://api.mailgun.net/v3/{EMAIL_DOMAIN}/messages"

    to_list = _filter_valid_emails(_recipients_to_list(to), field_name="to")
    if not to_list:
        logger.warning("Email not sent: no valid 'to' recipients")
        return None

    data = {
        "from": _get_from_email(),
        "to": _format_like_original(to, to_list),
        "subject": subject,
        "html": html,
    }

    reply_to_list = _build_reply_to(reply_to)
    if reply_to_list:
        data["h:Reply-To"] = ", ".join(reply_to_list)

    if cc is not None:
        cc_list = _filter_valid_emails(_recipients_to_list(cc), field_name="cc")
        if cc_list:
            data["cc"] = _format_like_original(cc, cc_list)

    deduped_bcc = _build_bcc_recipients(bcc)
    if deduped_bcc:
        data["bcc"] = deduped_bcc

    files = None
    if attachments:
        files = []
        for filename, content, mimetype in attachments:
            files.append(("attachment", (filename, content, mimetype)))

    try:
        response = requests.post(
            url,
            auth=("api", EMAIL_API_KEY),
            data=data,
            files=files,
            timeout=10,
        )
        _log_provider_response(provider="mailgun", response=response)
        return response
    except requests.RequestException:
        logger.exception("Mailgun request failed")
        return None


def _send_via_smtp(
    to: Union[str, Sequence[str]],
    subject: str,
    html: str,
    attachments: Optional[Sequence[Attachment]] = None,
    cc: RecipientInput = None,
    bcc: RecipientInput = None,
    reply_to: RecipientInput = None,
) -> Optional[DeliveryResult]:
    to_list = _filter_valid_emails(_recipients_to_list(to), field_name="to")
    if not to_list:
        logger.warning("SMTP email not sent: no valid 'to' recipients")
        return None

    cc_list = _filter_valid_emails(_recipients_to_list(cc), field_name="cc") if cc is not None else []
    bcc_list = _build_bcc_recipients(bcc)
    reply_to_list = _build_reply_to(reply_to)

    message = EmailMultiAlternatives(
        subject=subject,
        body=strip_tags(html) or html,
        from_email=_get_from_email(),
        to=to_list,
        cc=cc_list,
        bcc=bcc_list,
        reply_to=reply_to_list,
    )
    message.attach_alternative(html, "text/html")

    if attachments:
        for filename, content, mimetype in attachments:
            message.attach(filename, content, mimetype)

    try:
        sent_count = message.send(fail_silently=False)
        text = f"SMTP email accepted by backend: sent_count={sent_count}"
        logger.info(text)
        return DeliveryResult(status_code=250, text=text, backend=_get_delivery_provider() or "smtp")
    except Exception:
        logger.exception("SMTP email send failed")
        return None


def _recipients_to_list(value: RecipientInput) -> List[str]:
    """Convert a recipient input into a normalized list of email strings.

    Accepts:
    - None/empty: returns []
    - iterable (list/tuple/set): strips each item
    - str: splits on commas and strips each part

    This is primarily used for merging static recipients (e.g. `BCC_RECIPIENTS`)
    with recipients passed into `send_email`.

    In the BCC merge flow, the final recipient list merges + trims + de-dupes
    (case-insensitive) while preserving order.
    """
    if not value:
        return []
    if isinstance(value, (list, tuple, set)):
        return [str(item).strip() for item in value if str(item).strip()]
    return [part.strip() for part in str(value).split(",") if part.strip()]


def _filter_valid_emails(emails: Sequence[str], field_name: str) -> List[str]:
    valid: List[str] = []
    for email in emails:
        candidate = str(email).strip()
        if not candidate:
            continue
        try:
            validate_email(candidate)
            valid.append(candidate)
        except ValidationError:
            logger.warning("Invalid email dropped for %s: %r", field_name, candidate)
    return valid


def _format_like_original(original: Any, emails: Sequence[str]) -> Union[str, List[str]]:
    if isinstance(original, str):
        return ", ".join(emails)
    return list(emails)


def send_email(
    to: Union[str, Sequence[str]],
    subject: str,
    html: str,
    attachments: Optional[Sequence[Attachment]] = None,
    cc: RecipientInput = None,
    bcc: RecipientInput = None,
    reply_to: RecipientInput = None,
    enabled: Any = None,
) -> EmailDeliveryResponse:
    """Send an email using the configured delivery transport.

    Args:
        to: Recipient email(s). Accepts a string (single or comma-separated) or an iterable of emails.
        subject: Email subject.
        html: HTML body.
        attachments: Optional list of tuples: (filename, file_bytes_or_fileobj, mimetype).
        cc: Optional CC recipient(s). Accepts a string (single or comma-separated) or an iterable of emails.
        bcc: Optional additional BCC recipient(s). Accepts a string (single or comma-separated) or an iterable.
            Note: `BCC_RECIPIENTS` (if defined) are always included when sending.
        reply_to: Optional reply-to recipient(s). Accepts a string (single or comma-separated) or an iterable.
            Note: `EMAIL_REPLY_TO` (if defined) is always included when sending.
        enabled: Optional application-level override. Truthy values force delivery,
            falsey values disable delivery, and `None` uses the configured transport.
    """
    delivery_provider = _get_delivery_provider(enabled=enabled)

    if delivery_provider is None:
        return None

    if delivery_provider == "mailgun":
        return _send_via_mailgun(
            to=to,
            subject=subject,
            html=html,
            attachments=attachments,
            cc=cc,
            bcc=bcc,
            reply_to=reply_to,
        )

    if delivery_provider in {"mailpit", "smtp"}:
        return _send_via_smtp(
            to=to,
            subject=subject,
            html=html,
            attachments=attachments,
            cc=cc,
            bcc=bcc,
            reply_to=reply_to,
        )

    logger.info("Email delivery is disabled. Email not sent.")
    logger.info("To: %s", to)
    logger.info("Subject: %s", subject)
    return None