# coding=utf-8
"""
run the test from the src/invesytoolbox directory:
pytest ../tests/test_email_phone.py -v
"""
import email
import json
from itertools import combinations
from pathlib import Path

import pytest
from invesytoolbox.itb_email_phone import (
    check_spam,
    compare_phonenumbers,
    create_email_message,
    process_phonenumber,
)

TESTDATA = Path(__file__).parent / 'testdata'
with open(TESTDATA / 'email_phone.json') as f:
    _data = json.load(f)

phonenumbers = _data['phonenumbers']
email_data = _data['email_data'][0]  # first entry is the original test data
email_msg_boundary = _data['email_msg_boundary']
email_msg_str = _data['email_msg_str']


def test_create_email_message():
    email_msg = create_email_message(**email_data)
    if not isinstance(email_msg, email.mime.multipart.MIMEMultipart):
        raise AssertionError('Email message is not email.mime.multipart.MIMEMultipart')
    email_msg.set_boundary(email_msg_boundary)
    assert email_msg_str == str(email_msg)


def test_create_email_message_header_injection():
    """Newlines in header fields must raise ValueError to prevent header injection."""
    base = dict(
        mail_from='sender@example.com',
        subject='Normal subject',
        text='Hello',
        mail_to='recipient@example.com',
    )

    # \r\n in Subject
    with pytest.raises(ValueError, match='Subject.*header injection'):
        create_email_message(**{**base, 'subject': 'Test\r\nBcc: evil@example.com'})

    # \n in From
    with pytest.raises(ValueError, match='From.*header injection'):
        create_email_message(**{**base, 'mail_from': 'sender@example.com\nBcc: evil@example.com'})

    # \r\n in To
    with pytest.raises(ValueError, match='To.*header injection'):
        create_email_message(**{**base, 'mail_to': 'to@example.com\r\nBcc: evil@example.com'})

    # \n in CC
    with pytest.raises(ValueError, match='CC.*header injection'):
        create_email_message(**{**base, 'cc': 'cc@example.com\nevil@example.com'})

    # \n in Reply-To
    with pytest.raises(ValueError, match='Reply-To.*header injection'):
        create_email_message(**{**base, 'reply_to': 'reply@example.com\nBcc: evil@example.com'})


def test_check_spam():
    spamwords = ['viagra', 'casino', 'winner']

    # no spam
    score = check_spam(
        text='Hello, this is a normal message.',
        spamwords=spamwords.copy()
    )
    assert score == 0.0

    # spamword in text
    score = check_spam(
        text='Buy viagra now!',
        spamwords=spamwords.copy()
    )
    assert score == 9.0

    # multiple spamwords increase score
    score = check_spam(
        text='Buy viagra at the casino!',
        spamwords=spamwords.copy()
    )
    assert score == 9.1

    # URL in subject triggers score + the appended 'https://' spamword matches in subject too
    score = check_spam(
        text='Hello',
        spamwords=spamwords.copy(),
        subject='Visit https://spam.com'
    )
    assert score == 9.1

    # URL in text (not allowed by default)
    score = check_spam(
        text='Visit https://example.com for details',
        spamwords=spamwords.copy()
    )
    assert score == 9.0

    # URL in text allowed
    score = check_spam(
        text='Visit https://example.com for details',
        spamwords=spamwords.copy(),
        allow_url=True
    )
    assert score == 0.0

    # spamword in subject
    score = check_spam(
        text='Hello',
        spamwords=spamwords.copy(),
        subject='You are a winner!'
    )
    assert score == 9.0


def test_compare_phonenumbers():
    # identical numbers in different formats
    assert compare_phonenumbers(
        number1='+43 699 123456789',
        number2='0699 123456789',
        country='AT'
    ) is True

    # different numbers
    assert compare_phonenumbers(
        number1='+43 699 123456789',
        number2='+43 1 456789',
        country='AT'
    ) is False

    # empty numbers
    assert compare_phonenumbers(number1='', number2='+43 1 456789') is False
    assert compare_phonenumbers(number1='+43 1 456789', number2='') is False
    assert compare_phonenumbers(number1='', number2='') is False

    # same number, same format
    assert compare_phonenumbers(
        number1='+12124567890',
        number2='+12124567890',
        country='US'
    ) is True


def test_process_phonenumber_unparseable():
    """Test that checkonly and empty_if_invalid are respected for unparseable input."""
    # default: returns error string
    result = process_phonenumber('abc', country='AT')
    assert 'not a phone number' in result

    # checkonly: returns False
    result = process_phonenumber('abc', country='AT', checkonly=True)
    assert result is False

    # empty_if_invalid: returns empty string
    result = process_phonenumber('abc', country='AT', empty_if_invalid=True)
    assert result == ''


def test_process_phonenumber():
    for pn, data in phonenumbers.items():
        for fmt in (
            'international',
            'national',
            'E164'
        ):
            assert data[fmt] == process_phonenumber(
                pn,
                numberfmt=fmt,
                country='AT'
            )


# use dedicated data for correct results
def not_yet_test_compare_phonenumbers():
    for pn, data in phonenumbers.items():
        for fmt in (
            'international',
            'national',
            'E164'
        ):
            for ignore_invalid in (True, False):
                for country in ('at', 'us'):
                    # first we compare the pairs, which should be different
                    for number_pair in list(
                        combinations(phonenumbers, r=2)
                    ):
                        number1, number2 = number_pair

                        message = (
                            f'1 - numbers: {number1}, {number2}, '
                            f'ignore_invalid: {ignore_invalid}, '
                            f'country: {country}, '
                            f'numberfmt: {fmt}'
                        )
                        assert compare_phonenumbers(
                            number1=number1,
                            number2=number2,
                            numberfmt=fmt,
                            country=country,
                            ignore_invalid=ignore_invalid
                        ) is not False

                    # now we compare the different versions of the same number,
                    # which should be identical except if invalid
                    for number1, numbers in phonenumbers.items():
                        number2 = numbers.get(fmt)
                        number1_processed = process_phonenumber(
                            phonenumber=number1,
                            numberfmt=fmt,
                            country=country,
                            omit_error_str=ignore_invalid
                        )
                        number2_processed = process_phonenumber(
                            phonenumber=number2,
                            numberfmt=fmt,
                            country=country,
                            omit_error_str=ignore_invalid
                        )

                        compare_result = True
                        invalid = False

                        for invalid_str in ('invalid', 'impossible'):
                            if (
                                invalid_str in number1_processed
                                or invalid_str in number2_processed
                            ):
                                invalid = True
                                break

                            if not ignore_invalid and invalid:
                                compare_result = False

                            message = (
                                f'2 - numbers: {number1} ({number1_processed}), '
                                f'{number2} ({number2_processed}), '
                                f'ignore_invalid: {ignore_invalid}, '
                                f'country: {country}, '
                                f'numberfmt: {fmt}'
                            )
                            assert compare_result == compare_phonenumbers(
                                        number1=number1,
                                        number2=number2,
                                        numberfmt=fmt,
                                        ignore_invalid=ignore_invalid
                            ), message
