from unittest.mock import ANY, Mock

import botocore
import pytest

from app import aws_ses_client
from app.clients.email import EmailClientNonRetryableException
from app.clients.email.aws_ses import (
    AwsSesClientException,
    AwsSesClientThrottlingSendRateException,
    get_aws_responses,
)


def test_should_return_correct_details_for_delivery():
    response_dict = get_aws_responses('Delivery')
    assert response_dict['message'] == 'Delivered'
    assert response_dict['notification_status'] == 'delivered'
    assert response_dict['notification_statistics_status'] == 'delivered'
    assert response_dict['success']


def test_should_return_correct_details_for_hard_bounced():
    response_dict = get_aws_responses('Permanent')
    assert response_dict['message'] == 'Hard bounced'
    assert response_dict['notification_status'] == 'permanent-failure'
    assert response_dict['notification_statistics_status'] == 'failure'
    assert not response_dict['success']


def test_should_return_correct_details_for_soft_bounced():
    response_dict = get_aws_responses('Temporary')
    assert response_dict['message'] == 'Soft bounced'
    assert response_dict['notification_status'] == 'temporary-failure'
    assert response_dict['notification_statistics_status'] == 'failure'
    assert not response_dict['success']


def test_should_return_correct_details_for_complaint():
    response_dict = get_aws_responses('Complaint')
    assert response_dict['message'] == 'Complaint'
    assert response_dict['notification_status'] == 'delivered'
    assert response_dict['notification_statistics_status'] == 'delivered'
    assert response_dict['success']


def test_should_be_none_if_unrecognised_status_code():
    with pytest.raises(KeyError) as e:
        get_aws_responses('99')
    assert '99' in str(e.value)


@pytest.mark.parametrize('reply_to_address, expected_value', [
    (None, []),
    ('foo@bar.com', ['foo@bar.com']),
    ('føøøø@bååååår.com', ['føøøø@xn--br-yiaaaaa.com'])
], ids=['empty', 'single_email', 'punycode'])
def test_send_email_handles_reply_to_address(notify_api, mocker, reply_to_address, expected_value):
    boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True)

    with notify_api.app_context():
        aws_ses_client.send_email(
            source=Mock(),
            to_addresses='to@address.com',
            subject=Mock(),
            body=Mock(),
            reply_to_address=reply_to_address
        )

    boto_mock.send_email.assert_called_once_with(
        Source=ANY,
        Destination=ANY,
        Message=ANY,
        ReplyToAddresses=expected_value
    )


def test_send_email_handles_punycode_to_address(notify_api, mocker):
    boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True)

    with notify_api.app_context():
        aws_ses_client.send_email(
            Mock(),
            to_addresses='føøøø@bååååår.com',
            subject=Mock(),
            body=Mock()
        )

    boto_mock.send_email.assert_called_once_with(
        Source=ANY,
        Destination={'ToAddresses': ['føøøø@xn--br-yiaaaaa.com'], 'CcAddresses': [], 'BccAddresses': []},
        Message=ANY,
        ReplyToAddresses=ANY
    )


def test_send_email_raises_invalid_parameter_value_error_as_EmailClientNonRetryableException(mocker):
    boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True)
    error_response = {
        'Error': {
            'Code': 'InvalidParameterValue',
            'Message': 'some error message from amazon',
            'Type': 'Sender'
        }
    }
    boto_mock.send_email.side_effect = botocore.exceptions.ClientError(error_response, 'opname')

    with pytest.raises(EmailClientNonRetryableException) as excinfo:
        aws_ses_client.send_email(
            source=Mock(),
            to_addresses='definitely@invalid_email.com',
            subject=Mock(),
            body=Mock()
        )

    assert 'some error message from amazon' in str(excinfo.value)


def test_send_email_raises_send_rate_throttling_as_AwsSesClientThrottlingSendRateException(mocker):
    boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True)
    error_response = {
        'Error': {
            'Code': 'Throttling',
            'Message': 'Maximum sending rate exceeded.',
            'Type': 'Sender'
        }
    }
    boto_mock.send_email.side_effect = botocore.exceptions.ClientError(error_response, 'opname')

    with pytest.raises(AwsSesClientThrottlingSendRateException):
        aws_ses_client.send_email(
            source=Mock(),
            to_addresses='foo@bar.com',
            subject=Mock(),
            body=Mock()
        )


def test_send_email_does_not_raise_AwsSesClientThrottlingSendRateException_if_non_send_rate_throttling(mocker):
    boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True)
    error_response = {
        'Error': {
            'Code': 'Throttling',
            'Message': 'Daily message quota exceeded',
            'Type': 'Sender'
        }
    }
    boto_mock.send_email.side_effect = botocore.exceptions.ClientError(error_response, 'opname')

    with pytest.raises(AwsSesClientException):
        aws_ses_client.send_email(
            source=Mock(),
            to_addresses='foo@bar.com',
            subject=Mock(),
            body=Mock()
        )


def test_send_email_raises_other_errs_as_AwsSesClientException(mocker):
    boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True)
    error_response = {
        'Error': {
            'Code': 'ServiceUnavailable',
            'Message': 'some error message from amazon',
            'Type': 'Sender'
        }
    }
    boto_mock.send_email.side_effect = botocore.exceptions.ClientError(error_response, 'opname')

    with pytest.raises(AwsSesClientException) as excinfo:
        aws_ses_client.send_email(
            source=Mock(),
            to_addresses='foo@bar.com',
            subject=Mock(),
            body=Mock()
        )

    assert 'some error message from amazon' in str(excinfo.value)
