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

[((21, 18, 21, 45), 'elasticsearch_metrics.metrics.Keyword', 'metrics.Keyword', (), '', False, 'from elasticsearch_metrics import metrics\n'), ((22, 14, 22, 41), 'elasticsearch_metrics.metrics.Keyword', 'metrics.Keyword', (), '', False, 'from elasticsearch_metrics import metrics\n'), ((23, 18, 23, 45), 'elasticsearch_metrics.metrics.Keyword', 'metrics.Keyword', (), '', False, 'from elasticsearch_metrics import metrics\n'), ((177, 5, 177, 39), 'mock.patch.object', 'mock.patch.object', ({(177, 23, 177, 31): 'timezone', (177, 33, 177, 38): '"""now"""'}, {}), "(timezone, 'now')", False, 'import mock\n'), ((188, 5, 188, 58), 'mock.patch.object', 'mock.patch.object', ({(188, 23, 188, 35): 'PreprintView', (188, 37, 188, 57): '"""get_index_template"""'}, {}), "(PreprintView, 'get_index_template')", False, 'import mock\n'), ((36, 15, 36, 35), 'datetime.date', 'dt.date', ({(36, 23, 36, 27): '2020', (36, 29, 36, 30): '2', (36, 32, 36, 34): '14'}, {}), '(2020, 2, 14)', True, 'import datetime as dt\n'), ((44, 15, 44, 35), 'datetime.date', 'dt.date', ({(44, 23, 44, 27): '2020', (44, 29, 44, 30): '2', (44, 32, 44, 34): '14'}, {}), '(2020, 2, 14)', True, 'import datetime as dt\n'), ((82, 20, 82, 52), 'tests.dummyapp.metrics.DummyMetric.get_index_template', 'DummyMetric.get_index_template', ({}, {}), '()', False, 'from tests.dummyapp.metrics import DummyMetric, DummyMetricWithExplicitTemplateName, DummyMetricWithExplicitTemplatePattern\n'), ((83, 20, 83, 76), 'tests.dummyapp.metrics.DummyMetricWithExplicitTemplateName.get_index_template', 'DummyMetricWithExplicitTemplateName.get_index_template', ({}, {}), '()', False, 'from tests.dummyapp.metrics import DummyMetric, DummyMetricWithExplicitTemplateName, DummyMetricWithExplicitTemplatePattern\n'), ((102, 19, 102, 51), 'tests.dummyapp.metrics.DummyMetric.get_index_template', 'DummyMetric.get_index_template', ({}, {}), '()', False, 'from tests.dummyapp.metrics import DummyMetric, DummyMetricWithExplicitTemplateName, DummyMetricWithExplicitTemplatePattern\n'), ((118, 19, 118, 75), 'tests.dummyapp.metrics.DummyMetricWithExplicitTemplateName.get_index_template', 'DummyMetricWithExplicitTemplateName.get_index_template', ({}, {}), '()', False, 'from tests.dummyapp.metrics import DummyMetric, DummyMetricWithExplicitTemplateName, DummyMetricWithExplicitTemplatePattern\n'), ((128, 19, 128, 78), 'tests.dummyapp.metrics.DummyMetricWithExplicitTemplatePattern.get_index_template', 'DummyMetricWithExplicitTemplatePattern.get_index_template', ({}, {}), '()', False, 'from tests.dummyapp.metrics import DummyMetric, DummyMetricWithExplicitTemplateName, DummyMetricWithExplicitTemplatePattern\n'), ((171, 20, 171, 44), 'datetime.datetime', 'dt.datetime', ({(171, 32, 171, 36): '2017', (171, 38, 171, 39): '8', (171, 41, 171, 43): '21'}, {}), '(2017, 8, 21)', True, 'import datetime as dt\n'), ((179, 19, 179, 43), 'datetime.datetime', 'dt.datetime', ({(179, 31, 179, 35): '2016', (179, 37, 179, 38): '8', (179, 40, 179, 42): '21'}, {}), '(2016, 8, 21)', True, 'import datetime as dt\n'), ((190, 43, 190, 54), 'mock.Mock', 'mock.Mock', ({}, {}), '()', False, 'import mock\n'), ((191, 44, 191, 55), 'mock.Mock', 'mock.Mock', ({}, {}), '()', False, 'import mock\n'), ((192, 8, 192, 83), 'elasticsearch_metrics.signals.pre_index_template_create.connect', 'signals.pre_index_template_create.connect', ({(192, 50, 192, 82): 'mock_pre_index_template_listener'}, {}), '(mock_pre_index_template_listener)', False, 'from elasticsearch_metrics import signals\n'), ((193, 8, 193, 85), 'elasticsearch_metrics.signals.post_index_template_create.connect', 'signals.post_index_template_create.connect', ({(193, 51, 193, 84): 'mock_post_index_template_listener'}, {}), '(mock_post_index_template_listener)', False, 'from elasticsearch_metrics import signals\n'), ((206, 33, 206, 44), 'mock.Mock', 'mock.Mock', ({}, {}), '()', False, 'import mock\n'), ((207, 34, 207, 45), 'mock.Mock', 'mock.Mock', ({}, {}), '()', False, 'import mock\n'), ((208, 8, 208, 77), 'elasticsearch_metrics.signals.pre_save.connect', 'signals.pre_save.connect', (), '', False, 'from elasticsearch_metrics import signals\n'), ((209, 8, 209, 79), 'elasticsearch_metrics.signals.post_save.connect', 'signals.post_save.connect', (), '', False, 'from elasticsearch_metrics import signals\n'), ((90, 13, 90, 40), 'pytest.raises', 'pytest.raises', ({(90, 27, 90, 39): 'RuntimeError'}, {}), '(RuntimeError)', False, 'import pytest\n'), ((95, 13, 95, 40), 'pytest.raises', 'pytest.raises', ({(95, 27, 95, 39): 'RuntimeError'}, {}), '(RuntimeError)', False, 'import pytest\n'), ((138, 22, 138, 49), 'elasticsearch_metrics.metrics.Keyword', 'metrics.Keyword', (), '', False, 'from elasticsearch_metrics import metrics\n'), ((259, 13, 259, 54), 'pytest.raises', 'pytest.raises', ({(259, 27, 259, 53): 'IndexTemplateNotFoundError'}, {}), '(IndexTemplateNotFoundError)', False, 'import pytest\n'), ((268, 13, 268, 55), 'pytest.raises', 'pytest.raises', ({(268, 27, 268, 54): 'IndexTemplateOutOfSyncError'}, {}), '(IndexTemplateOutOfSyncError)', False, 'import pytest\n'), ((51, 16, 51, 30), 'django.utils.timezone.now', 'timezone.now', ({}, {}), '()', False, 'from django.utils import timezone\n'), ((160, 25, 160, 56), 'elasticsearch_metrics.metrics.MetaField', 'metrics.MetaField', (), '', False, 'from elasticsearch_metrics import metrics\n')]