from unittest.mock import patch

import pytest

from notifications_utils.template import SubjectMixin, Template


class ConcreteImplementation:
    template_type = None

    # Can’t instantiate and test templates unless they implement __str__
    def __str__(self):
        pass


class ConcreteTemplate(ConcreteImplementation, Template):
    pass


class ConcreteTemplateWithSubject(SubjectMixin, ConcreteTemplate):
    pass


def test_class():
    assert (
        repr(ConcreteTemplate({"content": "hello ((name))"}))
        == 'ConcreteTemplate("hello ((name))", {})'
    )


def test_passes_through_template_attributes():
    assert ConcreteTemplate({"content": ""}).name is None
    assert (
        ConcreteTemplate({"content": "", "name": "Two week reminder"}).name
        == "Two week reminder"
    )
    assert ConcreteTemplate({"content": ""}).id is None
    assert ConcreteTemplate({"content": "", "id": "1234"}).id == "1234"
    assert ConcreteTemplate({"content": ""}).template_type is None


def test_passes_through_subject():
    assert (
        ConcreteTemplateWithSubject(
            {"content": "", "subject": "Your tax is due"}
        ).subject
        == "Your tax is due"
    )


def test_errors_for_missing_template_content():
    with pytest.raises(KeyError):
        ConcreteTemplate({})


@pytest.mark.parametrize("template", [0, 1, 2, True, False, None])
def test_errors_for_invalid_template_types(template):
    with pytest.raises(TypeError):
        ConcreteTemplate(template)


@pytest.mark.parametrize("values", [[], False])
def test_errors_for_invalid_values(values):
    with pytest.raises(TypeError):
        ConcreteTemplate({"content": ""}, values)


def test_matches_keys_to_placeholder_names():
    template = ConcreteTemplate({"content": "hello ((name))"})

    template.values = {"NAME": "Chris"}
    assert template.values == {"name": "Chris"}

    template.values = {"NAME": "Chris", "Town": "London"}
    assert template.values == {"name": "Chris", "Town": "London"}
    assert template.additional_data == {"Town"}

    template.values = None
    assert template.missing_data == ["name"]


@pytest.mark.parametrize(
    ("template_content", "template_subject", "expected"),
    [
        ("the quick brown fox", "jumps", []),
        ("the quick ((colour)) fox", "jumps", ["colour"]),
        ("the quick ((colour)) ((animal))", "jumps", ["colour", "animal"]),
        ("((colour)) ((animal)) ((colour)) ((animal))", "jumps", ["colour", "animal"]),
        ("the quick brown fox", "((colour))", ["colour"]),
        ("the quick ((colour)) ", "((animal))", ["animal", "colour"]),
        ("((colour)) ((animal)) ", "((colour)) ((animal))", ["colour", "animal"]),
        ("Dear ((name)), ((warning?? This is a warning))", "", ["name", "warning"]),
        ("((warning? one question mark))", "", ["warning? one question mark"]),
    ],
)
def test_extracting_placeholders(template_content, template_subject, expected):
    assert (
        ConcreteTemplateWithSubject(
            {"content": template_content, "subject": template_subject}
        ).placeholders
        == expected
    )


def test_random_variable_retrieve():
    template = ConcreteTemplate({"content": "content", "created_by": "now"})
    assert template.get_raw("created_by") == "now"
    assert template.get_raw("missing", default="random") == "random"
    assert template.get_raw("missing") is None


def test_compare_template():
    with patch(
        "notifications_utils.template_change.TemplateChange.__init__", return_value=None
    ) as mocked:
        old_template = ConcreteTemplate({"content": "faked"})
        new_template = ConcreteTemplate({"content": "faked"})
        old_template.compare_to(new_template)
        mocked.assert_called_once_with(old_template, new_template)
