"""AWS SES email backend."""

import json
import os
from typing import Any

try:
    import aioboto3
    import boto3
except ImportError:
    boto3 = None  # type: ignore[assignment]
    aioboto3 = None  # type: ignore[assignment]

from amsdal_mail.backends.base import BaseEmailBackend
from amsdal_mail.exceptions import ConfigurationError
from amsdal_mail.exceptions import EmailConnectionError
from amsdal_mail.exceptions import SendError
from amsdal_mail.message import EmailMessage
from amsdal_mail.status import RecipientStatus
from amsdal_mail.status import SendStatus


class SESBackend(BaseEmailBackend):
    """
    Email backend that sends messages via AWS Simple Email Service (SES).

    Uses boto3 for synchronous operations and aioboto3 for async operations.
    Sends raw MIME messages to support full email features including attachments.

    Note on Click/Open Tracking:
        SES does not support track_opens/track_clicks parameters directly in the API.
        To enable click and open tracking with SES:
        1. Create a Configuration Set in AWS SES Console
        2. Add Event Destinations (SNS, CloudWatch, Kinesis, or Firehose)
        3. Enable "Click" and "Open" event types in the configuration set
        4. Pass the configuration_set name when initializing this backend

        The track_opens and track_clicks fields in EmailMessage are included for
        compatibility with other backends (SendGrid, Mailgun) but are ignored by SES.
    """

    def __init__(
        self,
        *,
        region_name: str | None = None,
        aws_access_key_id: str | None = None,
        aws_secret_access_key: str | None = None,
        configuration_set: str | None = None,
        **kwargs: Any,
    ) -> None:
        """
        Initialize the SES backend.

        Args:
            region_name: AWS region (env: AWS_REGION or AWS_DEFAULT_REGION)
            aws_access_key_id: AWS access key (env: AWS_ACCESS_KEY_ID)
            aws_secret_access_key: AWS secret key (env: AWS_SECRET_ACCESS_KEY)
            configuration_set: SES configuration set name (env: AWS_SES_CONFIGURATION_SET)
            **kwargs: Additional backend options

        Raises:
            ImportError: If boto3 or aioboto3 is not installed
            ConfigurationError: If required configuration is missing
        """
        super().__init__(**kwargs)

        if boto3 is None:
            msg = 'boto3 is required for SES backend. Install with: pip install amsdal-mail[ses]'
            raise ImportError(msg)

        # Load configuration from environment if not provided
        self.region_name = (
            region_name or os.environ.get('AWS_REGION') or os.environ.get('AWS_DEFAULT_REGION', 'us-east-1')
        )
        self.aws_access_key_id = aws_access_key_id or os.environ.get('AWS_ACCESS_KEY_ID')
        self.aws_secret_access_key = aws_secret_access_key or os.environ.get('AWS_SECRET_ACCESS_KEY')
        self.configuration_set = configuration_set or os.environ.get('AWS_SES_CONFIGURATION_SET')

        # Validate configuration
        if not self.aws_access_key_id or not self.aws_secret_access_key:
            msg = (
                'AWS credentials are required. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY '
                'environment variables or pass them as arguments.'
            )
            raise ConfigurationError(msg)

        # Create boto3 client (lazy loaded in send_messages)
        self.client = None

    def _get_client(self):
        """Get or create boto3 SES client."""
        if self.client is None:
            self.client = boto3.client(
                'ses',
                region_name=self.region_name,
                aws_access_key_id=self.aws_access_key_id,
                aws_secret_access_key=self.aws_secret_access_key,
            )
        return self.client

    def _send_templated_message(self, client: Any, message: EmailMessage) -> dict[str, Any]:
        """
        Send a templated email via SES send_templated_email API.

        Args:
            client: boto3 SES client
            message: EmailMessage with template_id

        Returns:
            SES API response
        """
        # Prepare template data - combine global and per-recipient data
        template_data = message.get_template_data(str(message.to[0]))

        send_params: dict[str, Any] = {
            'Source': str(message.from_email),
            'Destination': {
                'ToAddresses': [str(addr) for addr in message.to],
            },
            'Template': message.template_id,
            'TemplateData': json.dumps(template_data),
        }

        # Add CC/BCC if present
        if message.cc:
            send_params['Destination']['CcAddresses'] = [str(addr) for addr in message.cc]
        if message.bcc:
            send_params['Destination']['BccAddresses'] = [str(addr) for addr in message.bcc]

        # Add reply-to if present
        if message.reply_to:
            send_params['ReplyToAddresses'] = [str(addr) for addr in message.reply_to]

        # Add configuration set if specified
        if self.configuration_set:
            send_params['ConfigurationSetName'] = self.configuration_set

        # Add tags if specified
        if message.tags:
            send_params['Tags'] = [{'Name': tag, 'Value': 'true'} for tag in message.tags]

        # Add metadata as tags
        if message.metadata:
            metadata_tags = [{'Name': f'metadata:{key}', 'Value': value} for key, value in message.metadata.items()]
            if 'Tags' in send_params:
                send_params['Tags'].extend(metadata_tags)
            else:
                send_params['Tags'] = metadata_tags

        return client.send_templated_email(**send_params)

    def _send_raw_message(self, client: Any, message: EmailMessage) -> dict[str, Any]:
        """
        Send a raw MIME email via SES send_raw_email API.

        Args:
            client: boto3 SES client
            message: EmailMessage

        Returns:
            SES API response
        """
        # Convert to MIME format
        mime_message = message.as_mime()

        # Prepare send_raw_email parameters
        raw_message = {'Data': mime_message.as_string()}

        send_params: dict[str, Any] = {
            'Source': str(message.from_email),
            'Destinations': message.recipients(),
            'RawMessage': raw_message,
        }

        # Add configuration set if specified
        if self.configuration_set:
            send_params['ConfigurationSetName'] = self.configuration_set

        # Add tags if specified
        if message.tags:
            send_params['Tags'] = [{'Name': tag, 'Value': 'true'} for tag in message.tags]

        # Add metadata as tags (SES supports up to 50 tags total)
        if message.metadata:
            metadata_tags = [{'Name': f'metadata:{key}', 'Value': value} for key, value in message.metadata.items()]
            if 'Tags' in send_params:
                send_params['Tags'].extend(metadata_tags)
            else:
                send_params['Tags'] = metadata_tags

        return client.send_raw_email(**send_params)

    def send_messages(self, email_messages: list[EmailMessage]) -> SendStatus:
        """
        Send one or more email messages via AWS SES.

        Supports both templated emails (via send_templated_email) and
        regular emails (via send_raw_email).

        Args:
            email_messages: List of EmailMessage instances to send

        Returns:
            SendStatus with message IDs and statuses from SES

        Raises:
            SendError: If sending fails and fail_silently is False
            EmailConnectionError: If connection to SES fails
        """
        status = SendStatus()

        if not email_messages:
            return status

        client = self._get_client()
        recipients = {}

        for message in email_messages:
            try:
                # Choose API based on whether template is used
                if message.template_id:
                    response = self._send_templated_message(client, message)
                else:
                    response = self._send_raw_message(client, message)

                # Extract message ID from SES response
                message_id = response.get('MessageId')

                if message_id:
                    # Mark all recipients as sent with the message ID
                    for recipient in message.recipients():
                        recipients[recipient] = RecipientStatus(
                            message_id=message_id,
                            status='sent',  # SES queues immediately, consider it sent
                        )

                    # Store raw ESP response
                    if not status.esp_response:
                        status.esp_response = response
                elif not self.fail_silently:
                    msg = 'SES did not return a MessageId'
                    raise SendError(msg)

            except Exception as e:
                # Mark all recipients as failed
                for recipient in message.recipients():
                    recipients[recipient] = RecipientStatus(
                        message_id=None,
                        status='failed',
                    )

                if not self.fail_silently:
                    if 'Client' in str(type(e).__name__):
                        msg = f'Failed to connect to AWS SES: {e}'
                        raise EmailConnectionError(msg) from e
                    msg = f'Failed to send email via SES: {e}'
                    raise SendError(msg) from e

        status.set_recipient_status(recipients)
        return status

    async def _asend_templated_message(self, client: Any, message: EmailMessage) -> dict[str, Any]:
        """
        Send a templated email via SES send_templated_email API (async).

        Args:
            client: aioboto3 SES client
            message: EmailMessage with template_id

        Returns:
            SES API response
        """
        # Prepare template data - combine global and per-recipient data
        template_data = message.get_template_data(str(message.to[0]))

        send_params: dict[str, Any] = {
            'Source': str(message.from_email),
            'Destination': {
                'ToAddresses': [str(addr) for addr in message.to],
            },
            'Template': message.template_id,
            'TemplateData': json.dumps(template_data),
        }

        # Add CC/BCC if present
        if message.cc:
            send_params['Destination']['CcAddresses'] = [str(addr) for addr in message.cc]
        if message.bcc:
            send_params['Destination']['BccAddresses'] = [str(addr) for addr in message.bcc]

        # Add reply-to if present
        if message.reply_to:
            send_params['ReplyToAddresses'] = [str(addr) for addr in message.reply_to]

        # Add configuration set if specified
        if self.configuration_set:
            send_params['ConfigurationSetName'] = self.configuration_set

        # Add tags if specified
        if message.tags:
            send_params['Tags'] = [{'Name': tag, 'Value': 'true'} for tag in message.tags]

        # Add metadata as tags
        if message.metadata:
            metadata_tags = [{'Name': f'metadata:{key}', 'Value': value} for key, value in message.metadata.items()]
            if 'Tags' in send_params:
                send_params['Tags'].extend(metadata_tags)
            else:
                send_params['Tags'] = metadata_tags

        return await client.send_templated_email(**send_params)

    async def _asend_raw_message(self, client: Any, message: EmailMessage) -> dict[str, Any]:
        """
        Send a raw MIME email via SES send_raw_email API (async).

        Args:
            client: aioboto3 SES client
            message: EmailMessage

        Returns:
            SES API response
        """
        # Convert to MIME format
        mime_message = message.as_mime()

        # Prepare send_raw_email parameters
        raw_message = {'Data': mime_message.as_string()}

        send_params: dict[str, Any] = {
            'Source': str(message.from_email),
            'Destinations': message.recipients(),
            'RawMessage': raw_message,
        }

        # Add configuration set if specified
        if self.configuration_set:
            send_params['ConfigurationSetName'] = self.configuration_set

        # Add tags if specified
        if message.tags:
            send_params['Tags'] = [{'Name': tag, 'Value': 'true'} for tag in message.tags]

        # Add metadata as tags (SES supports up to 50 tags total)
        if message.metadata:
            metadata_tags = [{'Name': f'metadata:{key}', 'Value': value} for key, value in message.metadata.items()]
            if 'Tags' in send_params:
                send_params['Tags'].extend(metadata_tags)
            else:
                send_params['Tags'] = metadata_tags

        return await client.send_raw_email(**send_params)

    async def asend_messages(self, email_messages: list[EmailMessage]) -> SendStatus:
        """
        Send one or more email messages via AWS SES asynchronously.

        Supports both templated emails (via send_templated_email) and
        regular emails (via send_raw_email).

        Args:
            email_messages: List of EmailMessage instances to send

        Returns:
            SendStatus with message IDs and statuses from SES

        Raises:
            SendError: If sending fails and fail_silently is False
            EmailConnectionError: If connection to SES fails
            ImportError: If aioboto3 is not installed
        """
        if aioboto3 is None:
            msg = 'aioboto3 is required for async SES. Install with: pip install amsdal-mail[ses]'
            raise ImportError(msg)

        status = SendStatus()

        if not email_messages:
            return status

        recipients = {}

        # Create async session
        session = aioboto3.Session(
            region_name=self.region_name,
            aws_access_key_id=self.aws_access_key_id,
            aws_secret_access_key=self.aws_secret_access_key,
        )

        async with session.client('ses') as client:
            for message in email_messages:
                try:
                    # Choose API based on whether template is used
                    if message.template_id:
                        response = await self._asend_templated_message(client, message)
                    else:
                        response = await self._asend_raw_message(client, message)

                    # Extract message ID from SES response
                    message_id = response.get('MessageId')

                    if message_id:
                        # Mark all recipients as sent with the message ID
                        for recipient in message.recipients():
                            recipients[recipient] = RecipientStatus(
                                message_id=message_id,
                                status='sent',  # SES queues immediately, consider it sent
                            )

                        # Store raw ESP response
                        if not status.esp_response:
                            status.esp_response = response
                    elif not self.fail_silently:
                        msg = 'SES did not return a MessageId'
                        raise SendError(msg)

                except Exception as e:
                    # Mark all recipients as failed
                    for recipient in message.recipients():
                        recipients[recipient] = RecipientStatus(
                            message_id=None,
                            status='failed',
                        )

                    if not self.fail_silently:
                        if 'Client' in str(type(e).__name__):
                            msg = f'Failed to connect to AWS SES: {e}'
                            raise EmailConnectionError(msg) from e
                        msg = f'Failed to send email via SES: {e}'
                        raise SendError(msg) from e

        status.set_recipient_status(recipients)
        return status
