import itertools
import string
import unicodedata
from functools import partial
from random import choice, randrange
from unittest.mock import Mock

import pytest
from ordered_set import OrderedSet

from notifications_utils import SMS_CHAR_COUNT_LIMIT
from notifications_utils.formatters import strip_and_remove_obscure_whitespace
from notifications_utils.recipients import (
    Cell,
    RecipientCSV,
    Row,
    first_column_headings,
)
from notifications_utils.template import EmailPreviewTemplate, SMSMessageTemplate


def _sample_template(template_type, content="foo"):
    return {
        "email": EmailPreviewTemplate(
            {"content": content, "subject": "bar", "template_type": "email"}
        ),
        "sms": SMSMessageTemplate({"content": content, "template_type": "sms"}),
    }.get(template_type)


def _index_rows(rows):
    return set(row.index for row in rows)


@pytest.mark.parametrize(
    ("template_type", "expected"),
    [
        ("email", ["email address"]),
        ("sms", ["phone number"]),
    ],
)
def test_recipient_column_headers(template_type, expected):
    recipients = RecipientCSV("", template=_sample_template(template_type))
    assert (
        (recipients.recipient_column_headers)
        == (first_column_headings[template_type])
        == (expected)
    )


@pytest.mark.parametrize(
    ("file_contents", "template_type", "expected"),
    [
        (
            "",
            "sms",
            [],
        ),
        (
            "phone number",
            "sms",
            [],
        ),
        (
            """
                phone number,name
                +44 123, test1
                +44 456,test2
            """,
            "sms",
            [
                [("phone number", "+44 123"), ("name", "test1")],
                [("phone number", "+44 456"), ("name", "test2")],
            ],
        ),
        (
            """
                phone number,name
                +44 123,
                +44 456
            """,
            "sms",
            [
                [("phone number", "+44 123"), ("name", None)],
                [("phone number", "+44 456"), ("name", None)],
            ],
        ),
        (
            """
                email address,name
                test@example.com,test1
                test2@example.com, test2
            """,
            "email",
            [
                [("email address", "test@example.com"), ("name", "test1")],
                [("email address", "test2@example.com"), ("name", "test2")],
            ],
        ),
        (
            """
                email address
                test@example.com,test1,red
                test2@example.com, test2,blue
            """,
            "email",
            [
                [("email address", "test@example.com"), (None, ["test1", "red"])],
                [("email address", "test2@example.com"), (None, ["test2", "blue"])],
            ],
        ),
        (
            """
                email address,name
                test@example.com,"test1"
                test2@example.com,"   test2    "
                test3@example.com," test3"
            """,
            "email",
            [
                [("email address", "test@example.com"), ("name", "test1")],
                [("email address", "test2@example.com"), ("name", "test2")],
                [("email address", "test3@example.com"), ("name", "test3")],
            ],
        ),
        (
            """
                email address,date,name
                test@example.com,"Nov 28, 2016",test1
                test2@example.com,"Nov 29, 2016",test2
            """,
            "email",
            [
                [
                    ("email address", "test@example.com"),
                    ("date", "Nov 28, 2016"),
                    ("name", "test1"),
                ],
                [
                    ("email address", "test2@example.com"),
                    ("date", "Nov 29, 2016"),
                    ("name", "test2"),
                ],
            ],
        ),
        (
            """
                phone number, list, list, list
                07900900001, cat, rat, gnat
                07900900002, dog, hog, frog
                07900900003, elephant
            """,
            "sms",
            [
                [("phone number", "07900900001"), ("list", ["cat", "rat", "gnat"])],
                [("phone number", "07900900002"), ("list", ["dog", "hog", "frog"])],
                [("phone number", "07900900003"), ("list", ["elephant", None, None])],
            ],
        ),
    ],
)
def test_get_rows(file_contents, template_type, expected):
    rows = list(
        RecipientCSV(file_contents, template=_sample_template(template_type)).rows
    )
    if not expected:
        assert rows == expected
    for index, row in enumerate(expected):
        assert len(rows[index].items()) == len(row)
        for key, value in row:
            assert rows[index].get(key).data == value


def test_get_rows_does_no_error_checking_of_rows_or_cells(mocker):
    has_error_mock = mocker.patch.object(Row, "has_error")
    has_bad_recipient_mock = mocker.patch.object(Row, "has_bad_recipient")
    has_missing_data_mock = mocker.patch.object(Row, "has_missing_data")
    cell_recipient_error_mock = mocker.patch.object(Cell, "recipient_error")

    recipients = RecipientCSV(
        """
            email address, name
            a@b.com,
            a@b.com, My Name
            a@b.com,


        """,
        template=_sample_template("email", "hello ((name))"),
        max_errors_shown=3,
    )

    rows = recipients.get_rows()
    for _ in range(3):
        assert next(rows).recipient == "a@b.com"

    assert has_error_mock.called is False
    assert has_bad_recipient_mock.called is False
    assert has_missing_data_mock.called is False
    assert cell_recipient_error_mock.called is False


def test_get_rows_only_iterates_over_file_once(mocker):
    row_mock = mocker.patch("notifications_utils.recipients.Row")

    recipients = RecipientCSV(
        """
            email address, name
            a@b.com,
            a@b.com, My Name
            a@b.com,


        """,
        template=_sample_template("email", "hello ((name))"),
    )

    rows = recipients.get_rows()
    for _ in range(3):
        next(rows)

    assert row_mock.call_count == 3
    assert recipients.rows_as_list is None


@pytest.mark.parametrize(
    ("file_contents", "template_type", "expected"),
    [
        (
            """
                phone number,name
                2348675309, test1
                +1234-867-5301,test2
                ,
            """,
            "sms",
            [
                {"index": 0, "message_too_long": False},
                {"index": 1, "message_too_long": False},
            ],
        ),
        (
            """
                email address,name,colour
                test@example.com,test1,blue
                test2@example.com, test2,red
            """,
            "email",
            [
                {"index": 0, "message_too_long": False},
                {"index": 1, "message_too_long": False},
            ],
        ),
    ],
)
def test_get_annotated_rows(file_contents, template_type, expected):
    recipients = RecipientCSV(
        file_contents,
        template=_sample_template(template_type, "hello ((name))"),
        max_initial_rows_shown=1,
    )
    for index, expected_row in enumerate(expected):
        annotated_row = list(recipients.rows)[index]
        assert annotated_row.index == expected_row["index"]
        assert annotated_row.message_too_long == expected_row["message_too_long"]
    assert len(list(recipients.rows)) == 2
    assert len(list(recipients.initial_rows)) == 1
    assert not recipients.has_errors


def test_get_rows_with_errors():
    recipients = RecipientCSV(
        """
            email address, name
            a@b.com,
            a@b.com,
            a@b.com,
            a@b.com,
            a@b.com,
            a@b.com,


        """,
        template=_sample_template("email", "hello ((name))"),
        max_errors_shown=3,
    )
    assert len(list(recipients.rows_with_errors)) == 6
    assert len(list(recipients.initial_rows_with_errors)) == 3
    assert recipients.has_errors


@pytest.mark.parametrize(
    ("template_type", "row_count", "header", "filler", "row_with_error"),
    [
        (
            "email",
            500,
            "email address\n",
            "test@example.com\n",
            "test at example dot com",
        ),
        ("sms", 500, "phone number\n", "2348675309\n", "12345"),
    ],
)
def test_big_list_validates_right_through(
    template_type, row_count, header, filler, row_with_error
):
    big_csv = RecipientCSV(
        header + (filler * (row_count - 1) + row_with_error),
        template=_sample_template(template_type),
        max_errors_shown=100,
        max_initial_rows_shown=3,
    )
    assert len(list(big_csv.rows)) == row_count
    assert _index_rows(big_csv.rows_with_bad_recipients) == {row_count - 1}  # 0 indexed
    assert _index_rows(big_csv.rows_with_errors) == {row_count - 1}
    assert len(list(big_csv.initial_rows_with_errors)) == 1
    assert big_csv.has_errors


@pytest.mark.parametrize(
    ("template_type", "row_count", "header", "filler"),
    [
        ("email", 50, "email address\n", "test@example.com\n"),
        ("sms", 50, "phone number\n", "07900900123\n"),
    ],
)
def test_check_if_message_too_long_for_sms_but_not_email_in_CSV(
    mocker, template_type, row_count, header, filler
):
    # we do not validate email size for CSVs to avoid performance issues
    RecipientCSV(
        header + filler * row_count,
        template=_sample_template(template_type),
        max_errors_shown=100,
        max_initial_rows_shown=3,
    )
    is_message_too_long = mocker.patch(
        "notifications_utils.template.Template.is_message_too_long", side_effect=False
    )
    if template_type == "email":
        is_message_too_long.assert_not_called
    else:
        is_message_too_long.called


def test_overly_big_list_stops_processing_rows_beyond_max(mocker):
    mock_strip_and_remove_obscure_whitespace = mocker.patch(
        "notifications_utils.recipients.strip_and_remove_obscure_whitespace",
        wraps=strip_and_remove_obscure_whitespace,
    )
    mock_insert_or_append_to_dict = mocker.patch(
        "notifications_utils.recipients.insert_or_append_to_dict"
    )

    big_csv = RecipientCSV(
        "phonenumber,name\n" + ("2348675309,example\n" * 123),
        template=_sample_template("sms", content="hello ((name))"),
    )
    big_csv.max_rows = 10

    # Our CSV has lots of rows…
    assert big_csv.too_many_rows
    assert len(big_csv) == 123

    # …but we’ve only called the expensive whitespace function on each
    # of the 2 cells in the first 10 rows
    assert len(mock_strip_and_remove_obscure_whitespace.call_args_list) == 20

    # …and we’ve only called the function which builds the internal data
    # structure once for each of the first 10 rows
    assert len(mock_insert_or_append_to_dict.call_args_list) == 10


def test_file_with_lots_of_empty_columns():
    process = Mock()

    lots_of_commas = "," * 10_000

    for row in RecipientCSV(
        f"phone_number{lots_of_commas}\n" + (f"07900900900{lots_of_commas}\n" * 100),
        template=_sample_template("sms"),
    ):
        assert [(key, cell.data) for key, cell in row.items()] == [
            # Note that we haven’t stored any of the empty cells
            ("phonenumber", "07900900900")
        ]
        process()

    assert process.call_count == 100


def test_empty_column_names():
    recipient_csv = RecipientCSV(
        """
            phone_number,,,name
            07900900123,foo,bar,baz
        """,
        template=_sample_template("sms"),
    )

    assert recipient_csv[0]["phone_number"].data == "07900900123"
    assert recipient_csv[0][""].data == ["foo", "bar"]
    assert recipient_csv[0]["name"].data == "baz"


@pytest.mark.parametrize(
    ("file_contents", "template", "expected_recipients", "expected_personalisation"),
    [
        (
            """
                phone number,name, date
                +44 123,test1,today
                +44456,    ,tomorrow
                ,,
                , ,
            """,
            _sample_template("sms", "hello ((name))"),
            ["+44 123", "+44456"],
            [{"name": "test1"}, {"name": None}],
        ),
        (
            """
                email address,name,colour
                test@example.com,test1,red
                testatexampledotcom,test2,blue
            """,
            _sample_template("email", "((colour))"),
            ["test@example.com", "testatexampledotcom"],
            [{"colour": "red"}, {"colour": "blue"}],
        ),
        (
            """
                email address
                test@example.com,test1,red
                testatexampledotcom,test2,blue
            """,
            _sample_template("email"),
            ["test@example.com", "testatexampledotcom"],
            [],
        ),
    ],
)
def test_get_recipient(
    file_contents, template, expected_recipients, expected_personalisation
):
    recipients = RecipientCSV(file_contents, template=template)

    for index, row in enumerate(expected_personalisation):
        for key, value in row.items():
            assert recipients[index].recipient == expected_recipients[index]
            assert recipients[index].personalisation.get(key) == value


@pytest.mark.parametrize(
    ("file_contents", "template", "expected_recipients", "expected_personalisation"),
    [
        (
            """
                email address,test
                test@example.com,test1,red
                testatexampledotcom,test2,blue
            """,
            _sample_template("email", "((test))"),
            [(0, "test@example.com"), (1, "testatexampledotcom")],
            [
                {"emailaddress": "test@example.com", "test": "test1"},
                {"emailaddress": "testatexampledotcom", "test": "test2"},
            ],
        )
    ],
)
def test_get_recipient_respects_order(
    file_contents, template, expected_recipients, expected_personalisation
):
    recipients = RecipientCSV(file_contents, template=template)

    for row, email in expected_recipients:
        assert (
            recipients[row].index,
            recipients[row].recipient,
            recipients[row].personalisation,
        ) == (
            row,
            email,
            expected_personalisation[row],
        )


@pytest.mark.parametrize(
    ("file_contents", "template_type", "expected", "expected_missing"),
    [
        ("", "sms", [], set(["phone number", "name"])),
        (
            """
                phone number,name
                2348675309,test1
                2348675309,test1
                2348675309,test1
            """,
            "sms",
            ["phone number", "name"],
            set(),
        ),
        (
            """
                email address,name,colour
            """,
            "email",
            ["email address", "name", "colour"],
            set(),
        ),
        (
            """
                email address,colour
            """,
            "email",
            ["email address", "colour"],
            set(["name"]),
        ),
        (
            """
                phone number,list,list,name,list
            """,
            "sms",
            ["phone number", "list", "name"],
            set(),
        ),
    ],
)
def test_column_headers(file_contents, template_type, expected, expected_missing):
    recipients = RecipientCSV(
        file_contents, template=_sample_template(template_type, "((name))")
    )
    assert recipients.column_headers == expected
    assert recipients.missing_column_headers == expected_missing
    assert recipients.has_errors == bool(expected_missing)


@pytest.mark.parametrize(
    "content",
    [
        "hello",
        "hello ((name))",
    ],
)
@pytest.mark.parametrize(
    ("file_contents", "template_type"),
    [
        pytest.param("", "sms", marks=pytest.mark.xfail),
        pytest.param("name", "sms", marks=pytest.mark.xfail),
        pytest.param("email address", "sms", marks=pytest.mark.xfail),
        ("phone number", "sms"),
        ("phone number,name", "sms"),
        ("email address", "email"),
        ("email address,name", "email"),
        ("PHONENUMBER", "sms"),
        ("email_address", "email"),
    ],
)
def test_recipient_column(content, file_contents, template_type):
    assert RecipientCSV(
        file_contents, template=_sample_template(template_type, content)
    ).has_recipient_columns


@pytest.mark.parametrize(
    (
        "file_contents",
        "template_type",
        "rows_with_bad_recipients",
        "rows_with_missing_data",
    ),
    [
        (
            """
                phone number,name,date
                2348675309,test1,test1
                2348675309,test1
                +44 123,test1,test1
                2348675309,test1,test1
                2348675309,test1
                +1644000000,test1,test1
                ,test1,test1
            """,
            "sms",
            {2, 5},
            {1, 4, 6},
        ),
        (
            """
                phone number,name
                2348675309,test1,test2
            """,
            "sms",
            set(),
            set(),
        ),
        (
            """
            """,
            "sms",
            set(),
            set(),
        ),
    ],
)
@pytest.mark.parametrize(
    "partial_instance",
    [
        partial(RecipientCSV),
        partial(RecipientCSV, allow_international_sms=False),
    ],
)
def test_bad_or_missing_data(
    file_contents,
    template_type,
    rows_with_bad_recipients,
    rows_with_missing_data,
    partial_instance,
):
    recipients = partial_instance(
        file_contents, template=_sample_template(template_type, "((date))")
    )
    assert _index_rows(recipients.rows_with_bad_recipients) == rows_with_bad_recipients
    assert _index_rows(recipients.rows_with_missing_data) == rows_with_missing_data
    if rows_with_bad_recipients or rows_with_missing_data:
        assert recipients.has_errors is True


# TODO, original test for number one had {0, 1, 2}, but it has morphed to {0, 1}
# Is +447900123 legit or not?  What changed?
@pytest.mark.parametrize(
    ("file_contents", "rows_with_bad_recipients"),
    [
        (
            """
            phone number
            +800000000000
            1234
            +447900123
        """,
            {0, 1},
        ),
        (
            """
            phone number, country
            1-202-234-0104, USA
            +12022340104, USA
            +23051234567, Mauritius
        """,
            set(),
        ),
    ],
)
def test_international_recipients(file_contents, rows_with_bad_recipients):
    recipients = RecipientCSV(
        file_contents,
        template=_sample_template("sms"),
        allow_international_sms=True,
    )
    assert _index_rows(recipients.rows_with_bad_recipients) == rows_with_bad_recipients


def test_errors_when_too_many_rows():
    recipients = RecipientCSV(
        "email address\n" + ("a@b.com\n" * 101),
        template=_sample_template("email"),
    )

    # Confirm the normal max_row limit
    assert recipients.max_rows == 100_000
    # Override to make this test faster
    recipients.max_rows = 100

    assert recipients.too_many_rows is True
    assert recipients.has_errors is True
    assert recipients.rows[99]["email_address"].data == "a@b.com"
    # We stop processing subsequent rows
    assert recipients.rows[100] is None


@pytest.mark.parametrize(
    ("file_contents", "template_type", "guestlist", "count_of_rows_with_errors"),
    [
        (
            """
                phone number
                2348675309
                2348675301
                2348675302
                2348675303
            """,
            "sms",
            ["+12348675309"],  # Same as first phone number but in different format
            3,
        ),
        (
            """
                phone number
                12348675309
                2348675301
                2348675302
            """,
            "sms",
            [
                "2348675309",
                "12348675301",
                "2348675302",
                "2341231234",
                "test@example.com",
            ],
            0,
        ),
        (
            """
                email address
                IN_GUESTLIST@EXAMPLE.COM
                not_in_guestlist@example.com
            """,
            "email",
            [
                "in_guestlist@example.com",
                "2348675309",
            ],  # Email case differs to the one in the CSV
            1,
        ),
    ],
)
def test_recipient_guestlist(
    file_contents, template_type, guestlist, count_of_rows_with_errors
):
    recipients = RecipientCSV(
        file_contents, template=_sample_template(template_type), guestlist=guestlist
    )

    if count_of_rows_with_errors:
        assert not recipients.allowed_to_send_to
    else:
        assert recipients.allowed_to_send_to

    # Make sure the guestlist isn’t emptied by reading it. If it’s an iterator then
    # there’s a risk that it gets emptied after being read once
    recipients.guestlist = (
        str(fake_number) for fake_number in range(7700900888, 7700900898)
    )
    list(recipients.guestlist)
    assert not recipients.allowed_to_send_to
    assert recipients.has_errors

    # An empty guestlist is treated as no guestlist at all
    recipients.guestlist = []
    assert recipients.allowed_to_send_to
    recipients.guestlist = itertools.chain()
    assert recipients.allowed_to_send_to


def test_detects_rows_which_result_in_overly_long_messages():
    template = SMSMessageTemplate(
        {"content": "((placeholder))", "template_type": "sms"},
        sender=None,
        prefix=None,
    )
    recipients = RecipientCSV(
        """
            phone number,placeholder
            2348675309,1
            2348675301,{one_under}
            2348675302,{exactly}
            2348675303,{one_over}
        """.format(
            one_under="a" * (SMS_CHAR_COUNT_LIMIT - 1),
            exactly="a" * SMS_CHAR_COUNT_LIMIT,
            one_over="a" * (SMS_CHAR_COUNT_LIMIT + 1),
        ),
        template=template,
    )
    assert _index_rows(recipients.rows_with_errors) == {3}
    assert _index_rows(recipients.rows_with_message_too_long) == {3}
    assert recipients.has_errors
    assert recipients[0].has_error_spanning_multiple_cells is False
    assert recipients[1].has_error_spanning_multiple_cells is False
    assert recipients[2].has_error_spanning_multiple_cells is False
    assert recipients[3].has_error_spanning_multiple_cells is True


def test_detects_rows_which_result_in_empty_messages():
    template = SMSMessageTemplate(
        {"content": "((show??content))", "template_type": "sms"},
        sender=None,
        prefix=None,
    )
    recipients = RecipientCSV(
        """
            phone number,show
            2348675309,yes
            2348675301,no
            2348675302,yes
        """,
        template=template,
    )
    assert _index_rows(recipients.rows_with_errors) == {1}
    assert _index_rows(recipients.rows_with_empty_message) == {1}
    assert recipients.has_errors
    assert recipients[0].has_error_spanning_multiple_cells is False
    assert recipients[1].has_error_spanning_multiple_cells is True
    assert recipients[2].has_error_spanning_multiple_cells is False


@pytest.mark.parametrize(
    ("key", "expected"),
    sum(
        [
            [(key, expected) for key in group]
            for expected, group in [
                (
                    "2348675309",
                    (
                        "phone number",
                        "   PHONENUMBER",
                        "phone_number",
                        "phone-number",
                        "phoneNumber",
                    ),
                ),
                (
                    "Jo",
                    (
                        "FIRSTNAME",
                        "first name",
                        "first_name ",
                        "first-name",
                        "firstName",
                    ),
                ),
                (
                    "Bloggs",
                    (
                        "Last    Name",
                        "LASTNAME",
                        "    last_name",
                        "last-name",
                        "lastName   ",
                    ),
                ),
            ]
        ],
        [],
    ),
)
def test_ignores_spaces_and_case_in_placeholders(key, expected):
    recipients = RecipientCSV(
        """
            phone number,FIRSTNAME, Last Name
            2348675309, Jo, Bloggs
        """,
        template=_sample_template(
            "sms", content="((phone_number)) ((First Name)) ((lastname))"
        ),
    )
    first_row = recipients[0]
    assert first_row.get(key).data == expected
    assert first_row[key].data == expected
    assert first_row.recipient == "2348675309"
    assert len(first_row.items()) == 3
    assert not recipients.has_errors

    assert recipients.missing_column_headers == set()
    recipients.placeholders = {"one", "TWO", "Thirty_Three"}
    assert recipients.missing_column_headers == {"one", "TWO", "Thirty_Three"}
    assert recipients.has_errors


@pytest.mark.parametrize(
    ("character", "name"),
    [
        (" ", "SPACE"),
        # these ones don’t have unicode names
        ("\n", None),  # newline
        ("\r", None),  # carriage return
        ("\t", None),  # tab
        ("\u180e", "MONGOLIAN VOWEL SEPARATOR"),
        ("\u200b", "ZERO WIDTH SPACE"),
        ("\u200c", "ZERO WIDTH NON-JOINER"),
        ("\u200d", "ZERO WIDTH JOINER"),
        ("\u2060", "WORD JOINER"),
        ("\ufeff", "ZERO WIDTH NO-BREAK SPACE"),
        # all the things
        (" \n\r\t\u000a\u000d\u180e\u200b\u200c\u200d\u2060\ufeff", None),
    ],
)
def test_ignores_leading_whitespace_in_file(character, name):
    if name is not None:
        assert unicodedata.name(character) == name

    recipients = RecipientCSV(
        "{}emailaddress\ntest@example.com".format(character),
        template=_sample_template("email"),
    )
    first_row = recipients[0]

    assert recipients.column_headers == ["emailaddress"]
    assert recipients.recipient_column_headers == ["email address"]
    assert recipients.missing_column_headers == set()
    assert recipients.placeholders == ["email address"]

    assert first_row.get("email address").data == "test@example.com"
    assert first_row["email address"].data == "test@example.com"
    assert first_row.recipient == "test@example.com"

    assert not recipients.has_errors


def test_error_if_too_many_recipients():
    recipients = RecipientCSV(
        "phone number,\n2348675309,\n2348675309,\n2348675309,",
        template=_sample_template("sms"),
        remaining_messages=2,
    )
    assert recipients.has_errors
    assert recipients.more_rows_than_can_send


def test_dont_error_if_too_many_recipients_not_specified():
    recipients = RecipientCSV(
        "phone number,\n2348675309,\n2348675309,\n2348675309,",
        template=_sample_template("sms"),
    )
    assert not recipients.has_errors
    assert not recipients.more_rows_than_can_send


@pytest.mark.parametrize(
    ("index", "expected_row"),
    [
        (
            0,
            {
                "phone number": "07700 90000 1",
                "colour": "red",
            },
        ),
        (
            1,
            {
                "phone_number": "07700 90000 2",
                "COLOUR": "green",
            },
        ),
        (
            2,
            {"p h o n e  n u m b e r": "07700 90000 3", "   colour   ": "blue"},
        ),
        pytest.param(
            3,
            {"phone number": "foo"},
            marks=pytest.mark.xfail(raises=IndexError),
        ),
        (
            -1,
            {"p h o n e  n u m b e r": "07700 90000 3", "   colour   ": "blue"},
        ),
    ],
)
def test_recipients_can_be_accessed_by_index(index, expected_row):
    recipients = RecipientCSV(
        """
            phone number, colour
            07700 90000 1, red
            07700 90000 2, green
            07700 90000 3, blue
        """,
        template=_sample_template("sms"),
    )
    for key, value in expected_row.items():
        assert recipients[index][key].data == value


@pytest.mark.parametrize("international_sms", [True, False])
def test_multiple_sms_recipient_columns(international_sms):
    recipients = RecipientCSV(
        """
            phone number, phone number, phone_number, foo
            234-867-5301, 234-867-5302, 234-867-5309, bar
        """,
        template=_sample_template("sms"),
        allow_international_sms=international_sms,
    )
    assert recipients.column_headers == ["phone number", "phone_number", "foo"]
    assert (
        recipients.column_headers_as_column_keys == dict(phonenumber="", foo="").keys()
    )
    assert recipients.rows[0].get("phone number").data == ("234-867-5309")
    assert recipients.rows[0].get("phone_number").data == ("234-867-5309")
    assert recipients.rows[0].get("phone number").error is None
    assert recipients.duplicate_recipient_column_headers == OrderedSet(
        ["phone number", "phone_number"]
    )
    assert recipients.has_errors


@pytest.mark.parametrize(
    "column_name",
    [
        "phone_number",
        "phonenumber",
        "phone number",
        "phone-number",
        "p h o n e  n u m b e r",
    ],
)
def test_multiple_sms_recipient_columns_with_missing_data(column_name):
    recipients = RecipientCSV(
        """
            names, phone number, {}
            "Joanna and Steve", 07900 900111
        """.format(column_name),
        template=_sample_template("sms"),
        allow_international_sms=True,
    )
    expected_column_headers = ["names", "phone number"]
    if column_name != "phone number":
        expected_column_headers.append(column_name)
    assert recipients.column_headers == expected_column_headers
    assert (
        recipients.column_headers_as_column_keys
        == dict(phonenumber="", names="").keys()
    )
    # A piece of weirdness uncovered: since rows are created before spaces in column names are normalised, when
    # there are duplicate recipient columns and there is data for only one of the columns, if the columns have the same
    # spacing, phone number data will be a list of this one phone number and None, while if the spacing style differs
    # between two duplicate column names, the phone number data will be None. If there are no duplicate columns
    # then our code finds the phone number well regardless of the spacing, so this should not affect our users.
    phone_number_data = None
    if column_name == "phone number":
        phone_number_data = ["07900 900111", None]
    assert recipients.rows[0]["phonenumber"].data == phone_number_data
    assert recipients.rows[0].get("phone number").error is None
    expected_duplicated_columns = ["phone number"]
    if column_name != "phone number":
        expected_duplicated_columns.append(column_name)
    assert recipients.duplicate_recipient_column_headers == OrderedSet(
        expected_duplicated_columns
    )
    assert recipients.has_errors


def test_multiple_email_recipient_columns():
    recipients = RecipientCSV(
        """
            EMAILADDRESS, email_address, foo
            one@two.com,  two@three.com, bar
        """,
        template=_sample_template("email"),
    )
    assert recipients.rows[0].get("email address").data == ("two@three.com")
    assert recipients.rows[0].get("email address").error is None
    assert recipients.has_errors
    assert recipients.duplicate_recipient_column_headers == OrderedSet(
        ["EMAILADDRESS", "email_address"]
    )
    assert recipients.has_errors


def test_displayed_rows_when_some_rows_have_errors():
    recipients = RecipientCSV(
        """
            email address, name
            a@b.com,
            a@b.com,
            a@b.com, My Name
            a@b.com,
            a@b.com,
        """,
        template=_sample_template("email", "((name))"),
        max_errors_shown=3,
    )

    assert len(list(recipients.displayed_rows)) == 3


def test_displayed_rows_when_there_are_no_rows_with_errors():
    recipients = RecipientCSV(
        """
            email address, name
            a@b.com, My Name
            a@b.com, My Name
            a@b.com, My Name
            a@b.com, My Name
        """,
        template=_sample_template("email", "((name))"),
        max_errors_shown=3,
    )

    assert len(list(recipients.displayed_rows)) == 4


def test_multi_line_placeholders_work():
    recipients = RecipientCSV(
        """
            email address, data
            a@b.com, "a\nb\n\nc"
        """,
        template=_sample_template("email", "((data))"),
    )

    assert recipients.rows[0].personalisation["data"] == "a\nb\n\nc"


def test_email_validation_speed():
    email_addresses = "\n".join(
        (
            "{a}{b}@example-{n}.com,Example,Thursday".format(
                n=randrange(1000),
                a=choice(string.ascii_letters),
                b=choice(string.ascii_letters),
            )
            for i in range(1000)
        )
    )
    recipients = RecipientCSV(
        "email address,name,day\n" + email_addresses,
        template=_sample_template(
            "email",
            content=f"""
                hello ((name)) today is ((day))
                here’s the letter ‘a’ 1000 times:
                {'a' * 1000}
            """,
        ),
    )
    for row in recipients:
        assert not row.has_error


@pytest.mark.parametrize("should_validate", [True, False])
def test_recipient_csv_checks_should_validate_flag(should_validate):
    template = _sample_template("sms")
    template.is_message_empty = Mock(return_value=False)

    recipients = RecipientCSV(
        """phone number,name
        2348675309, test1
        +447700 900 460,test2""",
        template=template,
        should_validate=should_validate,
    )

    recipients._get_error_for_field = Mock(return_value=None)

    list(recipients.get_rows())

    assert template.is_message_empty.called is should_validate
    assert recipients._get_error_for_field.called is should_validate
