import mock
import pytest
import datetime as dt
from django.utils import timezone
from elasticsearch_metrics import metrics
from elasticsearch_dsl import IndexTemplate

from elasticsearch_metrics import signals
from elasticsearch_metrics.exceptions import (
    IndexTemplateNotFoundError,
    IndexTemplateOutOfSyncError,
)
from tests.dummyapp.metrics import (
    DummyMetric,
    DummyMetricWithExplicitTemplateName,
    DummyMetricWithExplicitTemplatePattern,
)


class PreprintView(metrics.Metric):
    provider_id = metrics.Keyword(index=True)
    user_id = metrics.Keyword(index=True)
    preprint_id = metrics.Keyword(index=True)

    class Index:
        settings = {"refresh_interval": "-1"}

    class Meta:
        app_label = "dummyapp"
        template_name = "osf_metrics_preprintviews"
        template = "osf_metrics_preprintviews-*"


class TestGetIndexName:
    def test_get_index_name(self):
        date = dt.date(2020, 2, 14)
        assert (
            PreprintView.get_index_name(date=date)
            == "osf_metrics_preprintviews_2020.02.14"
        )

    def test_get_index_name_respects_date_format_setting(self, settings):
        settings.ELASTICSEARCH_METRICS_DATE_FORMAT = "%Y-%m-%d"
        date = dt.date(2020, 2, 14)
        assert (
            PreprintView.get_index_name(date=date)
            == "osf_metrics_preprintviews_2020-02-14"
        )

    def test_get_index_name_gets_index_for_today_by_default(self):
        today = timezone.now().date()
        today_formatted = today.strftime("%Y.%m.%d")
        assert PreprintView.get_index_name() == "osf_metrics_preprintviews_{}".format(
            today_formatted
        )


class TestGetIndexTemplate:
    def test_get_index_template_returns_template_with_correct_name_and_pattern(self):
        template = PreprintView.get_index_template()
        assert isinstance(template, IndexTemplate)
        assert template._template_name == "osf_metrics_preprintviews"
        assert "osf_metrics_preprintviews-*" in template.to_dict()["index_patterns"]

    def test_get_index_template_respects_index_settings(self):
        template = PreprintView.get_index_template()
        assert template._index.to_dict()["settings"] == {"refresh_interval": "-1"}

    def test_get_index_template_creates_template_with_mapping(self):
        template = PreprintView.get_index_template()
        mappings = template.to_dict()["mappings"]
        assert mappings["doc"]["_source"]["enabled"] is False
        properties = mappings["doc"]["properties"]
        assert "timestamp" in properties
        assert properties["timestamp"] == {"doc_values": True, "type": "date"}
        assert properties["provider_id"] == {"type": "keyword", "index": True}
        assert properties["user_id"] == {"type": "keyword", "index": True}
        assert properties["preprint_id"] == {"type": "keyword", "index": True}

    # regression test
    def test_mappings_are_not_shared(self):
        template1 = DummyMetric.get_index_template()
        template2 = DummyMetricWithExplicitTemplateName.get_index_template()
        assert "my_int" in template1.to_dict()["mappings"]["doc"]["properties"]
        assert "my_keyword" not in template1.to_dict()["mappings"]["doc"]["properties"]
        assert "my_int" not in template2.to_dict()["mappings"]["doc"]["properties"]
        assert "my_keyword" in template2.to_dict()["mappings"]["doc"]["properties"]

    def test_declaring_metric_with_no_app_label_or_template_name_errors(self):
        with pytest.raises(RuntimeError):

            class BadMetric(metrics.Metric):
                pass

        with pytest.raises(RuntimeError):

            class MyMetric(metrics.Metric):
                class Meta:
                    template_name = "osf_metrics_preprintviews"

    def test_get_index_template_default_template_name(self):
        template = DummyMetric.get_index_template()
        assert isinstance(template, IndexTemplate)
        assert template._template_name == "dummyapp_dummymetric"
        assert "dummyapp_dummymetric_*" in template.to_dict()["index_patterns"]

    def test_get_index_template_uses_app_label_in_class_meta(self):
        class MyMetric(metrics.Metric):
            class Meta:
                app_label = "myapp"

        template = MyMetric.get_index_template()
        assert template._template_name == "myapp_mymetric"

    def test_template_name_defined_with_no_template_falls_back_to_default_template(
        self
    ):
        template = DummyMetricWithExplicitTemplateName.get_index_template()
        # template name specified in class Meta
        assert template._template_name == "dummymetric"
        # template is not specified, so it's generated
        assert (
            "dummyapp_dummymetricwithexplicittemplatename_*"
            in template.to_dict()["index_patterns"]
        )

    def test_template_defined_with_no_template_name_falls_back_to_default_name(self):
        template = DummyMetricWithExplicitTemplatePattern.get_index_template()
        # template name specified in class Meta
        assert (
            template._template_name == "dummyapp_dummymetricwithexplicittemplatepattern"
        )
        # template is not specified, so it's generated
        assert "dummymetric-*" in template.to_dict()["index_patterns"]

    def test_inheritance(self):
        class MyBaseMetric(metrics.Metric):
            user_id = metrics.Keyword(index=True)

            class Index:
                settings = {"number_of_shards": 2}

            class Meta:
                abstract = True

        class ConcreteMetric(MyBaseMetric):
            class Meta:
                app_label = "dummyapp"

        template = ConcreteMetric.get_index_template()
        assert template._template_name == "dummyapp_concretemetric"
        assert template._index.to_dict()["settings"] == {"number_of_shards": 2}

    def test_source_may_be_enabled(self):
        class MyMetric(metrics.Metric):
            class Meta:
                app_label = "dummyapp"
                template_name = "mymetric"
                template = "mymetric-*"
                source = metrics.MetaField(enabled=True)

        template = MyMetric.get_index_template()

        template_dict = template.to_dict()
        doc = template_dict["mappings"]["doc"]
        assert doc["_source"]["enabled"] is True


class TestRecord:
    def test_calls_save(self, mock_save):
        timestamp = dt.datetime(2017, 8, 21)
        p = PreprintView.record(timestamp=timestamp, provider_id="abc12")
        assert mock_save.call_count == 1
        assert p.timestamp == timestamp
        assert p.provider_id == "abc12"

    @mock.patch.object(timezone, "now")
    def test_defaults_timestamp_to_now(self, mock_now, mock_save):
        fake_now = dt.datetime(2016, 8, 21)
        mock_now.return_value = fake_now

        p = PreprintView.record(provider_id="abc12")
        assert mock_save.call_count == 1
        assert p.timestamp == fake_now


class TestSignals:
    @mock.patch.object(PreprintView, "get_index_template")
    def test_create_metric_sends_signals(self, mock_get_index_template):
        mock_pre_index_template_listener = mock.Mock()
        mock_post_index_template_listener = mock.Mock()
        signals.pre_index_template_create.connect(mock_pre_index_template_listener)
        signals.post_index_template_create.connect(mock_post_index_template_listener)
        PreprintView.sync_index_template()
        assert mock_pre_index_template_listener.call_count == 1
        assert mock_post_index_template_listener.call_count == 1
        pre_call_kwargs = mock_pre_index_template_listener.call_args[1]
        assert "index_template" in pre_call_kwargs
        assert "using" in pre_call_kwargs

        post_call_kwargs = mock_pre_index_template_listener.call_args[1]
        assert "index_template" in post_call_kwargs
        assert "using" in post_call_kwargs

    def test_save_sends_signals(self, mock_save):
        mock_pre_save_listener = mock.Mock()
        mock_post_save_listener = mock.Mock()
        signals.pre_save.connect(mock_pre_save_listener, sender=PreprintView)
        signals.post_save.connect(mock_post_save_listener, sender=PreprintView)

        provider_id = "12345"
        user_id = "abcde"
        preprint_id = "zyxwv"
        doc = PreprintView(
            provider_id=provider_id, user_id=user_id, preprint_id=preprint_id
        )
        doc.save()

        assert mock_pre_save_listener.call_count == 1
        pre_save_kwargs = mock_pre_save_listener.call_args[1]
        assert isinstance(pre_save_kwargs["instance"], PreprintView)
        assert "index" in pre_save_kwargs
        assert "using" in pre_save_kwargs
        assert pre_save_kwargs["sender"] is PreprintView

        assert mock_post_save_listener.call_count == 1
        post_save_kwargs = mock_pre_save_listener.call_args[1]
        assert isinstance(post_save_kwargs["instance"], PreprintView)
        assert "index" in post_save_kwargs
        assert "using" in post_save_kwargs
        assert post_save_kwargs["sender"] is PreprintView


@pytest.mark.es
class TestIntegration:
    def test_init(self, client):
        PreprintView.init()
        name = PreprintView.get_index_name()
        mapping = client.indices.get_mapping(index=name)
        properties = mapping[name]["mappings"]["doc"]["properties"]
        assert properties["timestamp"] == {"type": "date"}
        assert properties["provider_id"] == {"type": "keyword"}
        assert properties["user_id"] == {"type": "keyword"}
        assert properties["preprint_id"] == {"type": "keyword"}

    def test_create_document(self, client):
        provider_id = "12345"
        user_id = "abcde"
        preprint_id = "zyxwv"
        doc = PreprintView(
            provider_id=provider_id, user_id=user_id, preprint_id=preprint_id
        )
        doc.save()
        document = PreprintView.get(id=doc.meta.id, index=PreprintView.get_index_name())
        # TODO flesh out this test more.  Try to query ES?
        assert document is not None

    def test_check_index_template(self):
        with pytest.raises(IndexTemplateNotFoundError):
            assert PreprintView.check_index_template() is False
        PreprintView.sync_index_template()
        assert PreprintView.check_index_template() is True

        # When settings change, template is out of sync
        PreprintView._index.settings(
            **{"refresh_interval": "1s", "number_of_shards": 1, "number_of_replicas": 2}
        )
        with pytest.raises(IndexTemplateOutOfSyncError) as excinfo:
            assert PreprintView.check_index_template() is False
        error = excinfo.value
        assert error.settings_in_sync is False
        assert error.mappings_in_sync is True
        assert error.patterns_in_sync is True

        PreprintView.sync_index_template()
        assert PreprintView.check_index_template() is True

[((522, 549), 'elasticsearch_metrics.metrics.Keyword', 'metrics.Keyword', ([], {'index': '(True)'}), '(index=True)\n', (537, 549), False, 'from elasticsearch_metrics import metrics\n'), ((564, 591), 'elasticsearch_metrics.metrics.Keyword', 'metrics.Keyword', ([], {'index': '(True)'}), '(index=True)\n', (579, 591), False, 'from elasticsearch_metrics import metrics\n'), ((610, 637), 'elasticsearch_metrics.metrics.Keyword', 'metrics.Keyword', ([], {'index': '(True)'}), '(index=True)\n', (625, 637), False, 'from elasticsearch_metrics import metrics\n'), ((6668, 6702), 'mock.patch.object', 'mock.patch.object', (['timezone', '"""now"""'], {}), "(timezone, 'now')\n", (6685, 6702), False, 'import mock\n'), ((7015, 7068), 'mock.patch.object', 'mock.patch.object', (['PreprintView', '"""get_index_template"""'], {}), "(PreprintView, 'get_index_template')\n", (7032, 7068), False, 'import mock\n'), ((927, 947), 'datetime.date', 'dt.date', (['(2020)', '(2)', '(14)'], {}), '(2020, 2, 14)\n', (934, 947), True, 'import datetime as dt\n'), ((1234, 1254), 'datetime.date', 'dt.date', (['(2020)', '(2)', '(14)'], {}), '(2020, 2, 14)\n', (1241, 1254), True, 'import datetime as dt\n'), ((2972, 3004), 'tests.dummyapp.metrics.DummyMetric.get_index_template', 'DummyMetric.get_index_template', ([], {}), '()\n', (3002, 3004), False, 'from tests.dummyapp.metrics import DummyMetric, DummyMetricWithExplicitTemplateName, DummyMetricWithExplicitTemplatePattern\n'), ((3025, 3081), 'tests.dummyapp.metrics.DummyMetricWithExplicitTemplateName.get_index_template', 'DummyMetricWithExplicitTemplateName.get_index_template', ([], {}), '()\n', (3079, 3081), False, 'from tests.dummyapp.metrics import DummyMetric, DummyMetricWithExplicitTemplateName, DummyMetricWithExplicitTemplatePattern\n'), ((3868, 3900), 'tests.dummyapp.metrics.DummyMetric.get_index_template', 'DummyMetric.get_index_template', ([], {}), '()\n', (3898, 3900), False, 'from tests.dummyapp.metrics import DummyMetric, DummyMetricWithExplicitTemplateName, DummyMetricWithExplicitTemplatePattern\n'), ((4499, 4555), 'tests.dummyapp.metrics.DummyMetricWithExplicitTemplateName.get_index_template', 'DummyMetricWithExplicitTemplateName.get_index_template', ([], {}), '()\n', (4553, 4555), False, 'from tests.dummyapp.metrics import DummyMetric, DummyMetricWithExplicitTemplateName, DummyMetricWithExplicitTemplatePattern\n'), ((4961, 5020), 'tests.dummyapp.metrics.DummyMetricWithExplicitTemplatePattern.get_index_template', 'DummyMetricWithExplicitTemplatePattern.get_index_template', ([], {}), '()\n', (5018, 5020), False, 'from tests.dummyapp.metrics import DummyMetric, DummyMetricWithExplicitTemplateName, DummyMetricWithExplicitTemplatePattern\n'), ((6442, 6466), 'datetime.datetime', 'dt.datetime', (['(2017)', '(8)', '(21)'], {}), '(2017, 8, 21)\n', (6453, 6466), True, 'import datetime as dt\n'), ((6789, 6813), 'datetime.datetime', 'dt.datetime', (['(2016)', '(8)', '(21)'], {}), '(2016, 8, 21)\n', (6800, 6813), True, 'import datetime as dt\n'), ((7185, 7196), 'mock.Mock', 'mock.Mock', ([], {}), '()\n', (7194, 7196), False, 'import mock\n'), ((7241, 7252), 'mock.Mock', 'mock.Mock', ([], {}), '()\n', (7250, 7252), False, 'import mock\n'), ((7261, 7336), 'elasticsearch_metrics.signals.pre_index_template_create.connect', 'signals.pre_index_template_create.connect', (['mock_pre_index_template_listener'], {}), '(mock_pre_index_template_listener)\n', (7302, 7336), False, 'from elasticsearch_metrics import signals\n'), ((7345, 7422), 'elasticsearch_metrics.signals.post_index_template_create.connect', 'signals.post_index_template_create.connect', (['mock_post_index_template_listener'], {}), '(mock_post_index_template_listener)\n', (7387, 7422), False, 'from elasticsearch_metrics import signals\n'), ((8013, 8024), 'mock.Mock', 'mock.Mock', ([], {}), '()\n', (8022, 8024), False, 'import mock\n'), ((8059, 8070), 'mock.Mock', 'mock.Mock', ([], {}), '()\n', (8068, 8070), False, 'import mock\n'), ((8079, 8148), 'elasticsearch_metrics.signals.pre_save.connect', 'signals.pre_save.connect', (['mock_pre_save_listener'], {'sender': 'PreprintView'}), '(mock_pre_save_listener, sender=PreprintView)\n', (8103, 8148), False, 'from elasticsearch_metrics import signals\n'), ((8157, 8228), 'elasticsearch_metrics.signals.post_save.connect', 'signals.post_save.connect', (['mock_post_save_listener'], {'sender': 'PreprintView'}), '(mock_post_save_listener, sender=PreprintView)\n', (8182, 8228), False, 'from elasticsearch_metrics import signals\n'), ((3511, 3538), 'pytest.raises', 'pytest.raises', (['RuntimeError'], {}), '(RuntimeError)\n', (3524, 3538), False, 'import pytest\n'), ((3621, 3648), 'pytest.raises', 'pytest.raises', (['RuntimeError'], {}), '(RuntimeError)\n', (3634, 3648), False, 'import pytest\n'), ((5410, 5437), 'elasticsearch_metrics.metrics.Keyword', 'metrics.Keyword', ([], {'index': '(True)'}), '(index=True)\n', (5425, 5437), False, 'from elasticsearch_metrics import metrics\n'), ((10135, 10176), 'pytest.raises', 'pytest.raises', (['IndexTemplateNotFoundError'], {}), '(IndexTemplateNotFoundError)\n', (10148, 10176), False, 'import pytest\n'), ((10551, 10593), 'pytest.raises', 'pytest.raises', (['IndexTemplateOutOfSyncError'], {}), '(IndexTemplateOutOfSyncError)\n', (10564, 10593), False, 'import pytest\n'), ((1471, 1485), 'django.utils.timezone.now', 'timezone.now', ([], {}), '()\n', (1483, 1485), False, 'from django.utils import timezone\n'), ((6138, 6169), 'elasticsearch_metrics.metrics.MetaField', 'metrics.MetaField', ([], {'enabled': '(True)'}), '(enabled=True)\n', (6155, 6169), False, 'from elasticsearch_metrics import metrics\n')]