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, 'elasticsearch_metrics.metrics.Keyword', 'metrics.Keyword', False, 'from elasticsearch_metrics import metrics\n'), (22, 'elasticsearch_metrics.metrics.Keyword', 'metrics.Keyword', False, 'from elasticsearch_metrics import metrics\n'), (23, 'elasticsearch_metrics.metrics.Keyword', 'metrics.Keyword', False, 'from elasticsearch_metrics import metrics\n'), (177, 'mock.patch.object', 'mock.patch.object', False, 'import mock\n'), (188, 'mock.patch.object', 'mock.patch.object', False, 'import mock\n'), (36, 'datetime.date', 'dt.date', True, 'import datetime as dt\n'), (44, 'datetime.date', 'dt.date', True, 'import datetime as dt\n'), (82, 'tests.dummyapp.metrics.DummyMetric.get_index_template', 'DummyMetric.get_index_template', False, 'from tests.dummyapp.metrics import DummyMetric, DummyMetricWithExplicitTemplateName, DummyMetricWithExplicitTemplatePattern\n'), (83, 'tests.dummyapp.metrics.DummyMetricWithExplicitTemplateName.get_index_template', 'DummyMetricWithExplicitTemplateName.get_index_template', False, 'from tests.dummyapp.metrics import DummyMetric, DummyMetricWithExplicitTemplateName, DummyMetricWithExplicitTemplatePattern\n'), (102, 'tests.dummyapp.metrics.DummyMetric.get_index_template', 'DummyMetric.get_index_template', False, 'from tests.dummyapp.metrics import DummyMetric, DummyMetricWithExplicitTemplateName, DummyMetricWithExplicitTemplatePattern\n'), (118, 'tests.dummyapp.metrics.DummyMetricWithExplicitTemplateName.get_index_template', 'DummyMetricWithExplicitTemplateName.get_index_template', False, 'from tests.dummyapp.metrics import DummyMetric, DummyMetricWithExplicitTemplateName, DummyMetricWithExplicitTemplatePattern\n'), (128, 'tests.dummyapp.metrics.DummyMetricWithExplicitTemplatePattern.get_index_template', 'DummyMetricWithExplicitTemplatePattern.get_index_template', False, 'from tests.dummyapp.metrics import DummyMetric, DummyMetricWithExplicitTemplateName, DummyMetricWithExplicitTemplatePattern\n'), (171, 'datetime.datetime', 'dt.datetime', True, 'import datetime as dt\n'), (179, 'datetime.datetime', 'dt.datetime', True, 'import datetime as dt\n'), (190, 'mock.Mock', 'mock.Mock', False, 'import mock\n'), (191, 'mock.Mock', 'mock.Mock', False, 'import mock\n'), (192, 'elasticsearch_metrics.signals.pre_index_template_create.connect', 'signals.pre_index_template_create.connect', False, 'from elasticsearch_metrics import signals\n'), (193, 'elasticsearch_metrics.signals.post_index_template_create.connect', 'signals.post_index_template_create.connect', False, 'from elasticsearch_metrics import signals\n'), (206, 'mock.Mock', 'mock.Mock', False, 'import mock\n'), (207, 'mock.Mock', 'mock.Mock', False, 'import mock\n'), (208, 'elasticsearch_metrics.signals.pre_save.connect', 'signals.pre_save.connect', False, 'from elasticsearch_metrics import signals\n'), (209, 'elasticsearch_metrics.signals.post_save.connect', 'signals.post_save.connect', False, 'from elasticsearch_metrics import signals\n'), (90, 'pytest.raises', 'pytest.raises', False, 'import pytest\n'), (95, 'pytest.raises', 'pytest.raises', False, 'import pytest\n'), (138, 'elasticsearch_metrics.metrics.Keyword', 'metrics.Keyword', False, 'from elasticsearch_metrics import metrics\n'), (259, 'pytest.raises', 'pytest.raises', False, 'import pytest\n'), (268, 'pytest.raises', 'pytest.raises', False, 'import pytest\n'), (51, 'django.utils.timezone.now', 'timezone.now', False, 'from django.utils import timezone\n'), (160, 'elasticsearch_metrics.metrics.MetaField', 'metrics.MetaField', False, 'from elasticsearch_metrics import metrics\n')]