"""AWS SES webhook parser via SNS notifications."""

import json
import logging
from datetime import datetime
from typing import Any

from amsdal_mail.events import EmailTrackingContext
from amsdal_mail.events import TrackingEventType
from amsdal_mail.webhooks.base import BaseWebhookParser

logger = logging.getLogger(__name__)

_SES_EVENT_MAP: dict[str, TrackingEventType] = {
    'Send': TrackingEventType.SENT,
    'Delivery': TrackingEventType.DELIVERED,
    'Bounce': TrackingEventType.BOUNCED,
    'Complaint': TrackingEventType.COMPLAINED,
    'Reject': TrackingEventType.REJECTED,
    'Open': TrackingEventType.OPENED,
    'Click': TrackingEventType.CLICKED,
    'DeliveryDelay': TrackingEventType.DEFERRED,
}


class SESWebhookParser(BaseWebhookParser):
    """Parse SES event notifications delivered via SNS.

    SES sends events to an SNS topic, which then delivers them
    as HTTP POST requests to the webhook endpoint. The payload
    is an SNS message envelope containing the SES event JSON.

    Args:
        secret: Optional secret for HTTP basic auth verification.
    """

    def __init__(self, secret: str | None = None) -> None:
        self.secret = secret

    def verify(self, body: bytes, headers: dict[str, str]) -> bool:
        """Verify SNS message by cross-validating headers against body.

        Checks that SNS headers (x-amz-sns-message-type, x-amz-sns-message-id)
        match the corresponding fields in the JSON body (Type, MessageId).
        This prevents trivial forgery where only headers are spoofed.

        TODO: add signature verification https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html
        """
        header_type = headers.get('x-amz-sns-message-type')
        header_id = headers.get('x-amz-sns-message-id')

        if not header_type or not header_id:
            return False

        if header_type not in ('SubscriptionConfirmation', 'Notification', 'UnsubscribeConfirmation'):
            return False

        try:
            sns_message = json.loads(body)
        except (json.JSONDecodeError, UnicodeDecodeError):
            return False

        body_type = sns_message.get('Type')
        body_id = sns_message.get('MessageId')

        if header_type != body_type or header_id != body_id:
            return False

        return True

    def parse(self, body: bytes, headers: dict[str, str]) -> list[EmailTrackingContext]:
        """Parse SNS notification containing SES event.

        Handles both SubscriptionConfirmation (returns empty list)
        and Notification messages (returns tracking events).
        """
        message_type = headers.get('x-amz-sns-message-type', '')

        sns_message = json.loads(body)

        if message_type == 'SubscriptionConfirmation':
            logger.info('SNS subscription confirmation received. SubscribeURL: %s', sns_message.get('SubscribeURL'))
            return []

        if message_type == 'UnsubscribeConfirmation':
            logger.info('SNS unsubscribe confirmation received.')
            return []

        # Notification - parse the SES event from the Message field
        ses_event = json.loads(sns_message.get('Message', '{}'))

        return self._parse_ses_event(ses_event)

    def _parse_ses_event(self, ses_event: dict[str, Any]) -> list[EmailTrackingContext]:
        """Parse SES event JSON into tracking contexts.

        Expands multi-recipient events (e.g. a bounce with 3 recipients)
        into individual EmailTrackingContext instances.
        """
        event_type_str = ses_event.get('eventType', '')
        tracking_type = _SES_EVENT_MAP.get(event_type_str, TrackingEventType.UNKNOWN)

        mail_obj = ses_event.get('mail', {})
        message_id = mail_obj.get('messageId', '')
        tags = self._extract_tags(mail_obj)
        metadata = self._extract_metadata(mail_obj)

        detail_key = self._get_detail_key(event_type_str)
        detail = ses_event.get(detail_key, {}) if detail_key else {}

        timestamp = self._parse_timestamp(detail, ses_event)
        recipients = self._extract_recipients(event_type_str, detail, mail_obj)
        reject_reason = self._extract_reject_reason(event_type_str, detail)
        description = self._extract_description(event_type_str, detail)
        click_url = detail.get('link') if event_type_str == 'Click' else None
        user_agent = detail.get('userAgent') if event_type_str in ('Open', 'Click') else None

        contexts = []
        for recipient in recipients:
            contexts.append(
                EmailTrackingContext(
                    event_type=tracking_type,
                    message_id=message_id,
                    recipient=recipient,
                    timestamp=timestamp,
                    esp_name='ses',
                    tags=tags,
                    metadata=metadata,
                    reject_reason=reject_reason,
                    description=description,
                    click_url=click_url,
                    user_agent=user_agent,
                    raw_event=ses_event,
                )
            )

        return contexts

    def _get_detail_key(self, event_type: str) -> str | None:
        key_map = {
            'Bounce': 'bounce',
            'Complaint': 'complaint',
            'Delivery': 'delivery',
            'Send': 'send',
            'Reject': 'reject',
            'Open': 'open',
            'Click': 'click',
            'DeliveryDelay': 'deliveryDelay',
        }
        return key_map.get(event_type)

    def _parse_timestamp(self, detail: dict[str, Any], ses_event: dict[str, Any]) -> datetime:
        ts_str = detail.get('timestamp') or ses_event.get('mail', {}).get('timestamp', '')
        if ts_str:
            return datetime.fromisoformat(ts_str.replace('Z', '+00:00'))
        return datetime.now()  # noqa: DTZ005

    def _extract_recipients(
        self, event_type: str, detail: dict[str, Any], mail_obj: dict[str, Any]
    ) -> list[str]:
        if event_type == 'Bounce':
            return [r.get('emailAddress', '') for r in detail.get('bouncedRecipients', [])]
        if event_type == 'Complaint':
            return [r.get('emailAddress', '') for r in detail.get('complainedRecipients', [])]
        if event_type == 'Delivery':
            return detail.get('recipients', [])
        # For Send, Reject, Open, Click, DeliveryDelay - use mail destination
        return mail_obj.get('destination', [])

    def _extract_tags(self, mail_obj: dict[str, Any]) -> list[str]:
        tags_dict = mail_obj.get('tags', {})
        result = []
        for key, values in tags_dict.items():
            for val in values:
                result.append(f'{key}:{val}')
        return result

    def _extract_metadata(self, mail_obj: dict[str, Any]) -> dict[str, Any]:
        headers = mail_obj.get('headers', [])
        metadata: dict[str, Any] = {}
        for header in headers:
            name = header.get('name', '')
            if name.startswith('X-Metadata-'):
                metadata[name.removeprefix('X-Metadata-')] = header.get('value', '')
        return metadata

    def _extract_reject_reason(self, event_type: str, detail: dict[str, Any]) -> str | None:
        if event_type == 'Bounce':
            return detail.get('bounceSubType') or detail.get('bounceType')
        if event_type == 'Reject':
            return detail.get('reason')
        return None

    def _extract_description(self, event_type: str, detail: dict[str, Any]) -> str | None:
        if event_type == 'Bounce':
            recipients = detail.get('bouncedRecipients', [])
            if recipients:
                return recipients[0].get('diagnosticCode')
        if event_type == 'DeliveryDelay':
            return detail.get('delayType')
        return None
