import json
import os
from difflib import SequenceMatcher

import pytest
import yaml
from click.testing import CliRunner

from dagster import seven
from dagster.cli.pipeline import pipeline_execute_command
from dagster.core.definitions.reconstructable import get_ephemeral_repository_name
from dagster.core.instance import DagsterInstance
from dagster.core.telemetry import UPDATE_REPO_STATS, get_dir_from_dagster_home, hash_name
from dagster.core.test_utils import environ
from dagster.utils import file_relative_path, pushd, script_relative_path

EXPECTED_KEYS = set(
    [
        'action',
        'client_time',
        'elapsed_time',
        'event_id',
        'instance_id',
        'pipeline_name_hash',
        'num_pipelines_in_repo',
        'repo_hash',
        'python_version',
        'metadata',
        'version',
    ]
)


def path_to_file(path):
    return script_relative_path(os.path.join('./', path))


@pytest.mark.skipif(
    os.name == 'nt', reason="TemporaryDirectory disabled for win because of event.log contention"
)
def test_dagster_telemetry_enabled(caplog):
    with seven.TemporaryDirectory() as temp_dir:
        with environ({'DAGSTER_HOME': temp_dir}):
            with open(os.path.join(temp_dir, 'dagster.yaml'), 'w') as fd:
                yaml.dump({'telemetry': {'enabled': True}}, fd, default_flow_style=False)

            DagsterInstance.local_temp(temp_dir)
            runner = CliRunner(env={'DAGSTER_HOME': temp_dir})
            with pushd(path_to_file('')):
                pipeline_attribute = 'foo_pipeline'
                pipeline_name = 'foo'
                result = runner.invoke(
                    pipeline_execute_command,
                    ['-f', path_to_file('test_cli_commands.py'), '-a', pipeline_attribute,],
                )

                for record in caplog.records:
                    message = json.loads(record.getMessage())
                    if message.get('action') == UPDATE_REPO_STATS:
                        assert message.get('pipeline_name_hash') == hash_name(pipeline_name)
                        assert message.get('num_pipelines_in_repo') == str(1)
                        assert message.get('repo_hash') == hash_name(
                            get_ephemeral_repository_name(pipeline_name)
                        )
                    assert set(message.keys()) == EXPECTED_KEYS
                assert len(caplog.records) == 5
                assert result.exit_code == 0


@pytest.mark.skipif(
    os.name == 'nt', reason="TemporaryDirectory disabled for win because of event.log contention"
)
def test_dagster_telemetry_disabled(caplog):
    with seven.TemporaryDirectory() as temp_dir:
        with environ({'DAGSTER_HOME': temp_dir}):
            with open(os.path.join(temp_dir, 'dagster.yaml'), 'w') as fd:
                yaml.dump({'telemetry': {'enabled': False}}, fd, default_flow_style=False)

            DagsterInstance.local_temp(temp_dir)

            runner = CliRunner(env={'DAGSTER_HOME': temp_dir})
            with pushd(path_to_file('')):
                pipeline_name = 'foo_pipeline'
                result = runner.invoke(
                    pipeline_execute_command,
                    ['-f', path_to_file('test_cli_commands.py'), '-a', pipeline_name,],
                )

            assert not os.path.exists(os.path.join(get_dir_from_dagster_home('logs'), 'event.log'))
            assert len(caplog.records) == 0
            assert result.exit_code == 0


@pytest.mark.skipif(
    os.name == 'nt', reason="TemporaryDirectory disabled for win because of event.log contention"
)
def test_dagster_telemetry_unset(caplog):
    with seven.TemporaryDirectory() as temp_dir:
        with environ({'DAGSTER_HOME': temp_dir}):
            with open(os.path.join(temp_dir, 'dagster.yaml'), 'w') as fd:
                yaml.dump({}, fd, default_flow_style=False)

            DagsterInstance.local_temp(temp_dir)
            runner = CliRunner(env={'DAGSTER_HOME': temp_dir})
            with pushd(path_to_file('')):
                pipeline_attribute = 'foo_pipeline'
                pipeline_name = 'foo'
                result = runner.invoke(
                    pipeline_execute_command,
                    ['-f', path_to_file('test_cli_commands.py'), '-a', pipeline_attribute],
                )

                for record in caplog.records:
                    message = json.loads(record.getMessage())
                    if message.get('action') == UPDATE_REPO_STATS:
                        assert message.get('pipeline_name_hash') == hash_name(pipeline_name)
                        assert message.get('num_pipelines_in_repo') == str(1)
                        assert message.get('repo_hash') == hash_name(
                            get_ephemeral_repository_name(pipeline_name)
                        )
                    assert set(message.keys()) == EXPECTED_KEYS

                assert len(caplog.records) == 5
                assert result.exit_code == 0


@pytest.mark.skipif(
    os.name == 'nt', reason="TemporaryDirectory disabled for win because of event.log contention"
)
def test_repo_stats(caplog):
    with seven.TemporaryDirectory() as temp_dir:
        with environ({'DAGSTER_HOME': temp_dir}):
            with open(os.path.join(temp_dir, 'dagster.yaml'), 'w') as fd:
                yaml.dump({}, fd, default_flow_style=False)

            DagsterInstance.local_temp(temp_dir)
            runner = CliRunner(env={'DAGSTER_HOME': temp_dir})
            with pushd(path_to_file('')):
                pipeline_name = 'multi_mode_with_resources'
                result = runner.invoke(
                    pipeline_execute_command,
                    [
                        '-f',
                        file_relative_path(__file__, '../../general_tests/test_repository.py'),
                        '-a',
                        'dagster_test_repository',
                        '-p',
                        pipeline_name,
                        '--preset',
                        'add',
                        '--tags',
                        '{ "foo": "bar" }',
                    ],
                )

                assert result.exit_code == 0, result.stdout

                for record in caplog.records:
                    message = json.loads(record.getMessage())
                    if message.get('action') == UPDATE_REPO_STATS:
                        assert message.get('pipeline_name_hash') == hash_name(pipeline_name)
                        assert message.get('num_pipelines_in_repo') == str(4)
                        assert message.get('repo_hash') == hash_name('dagster_test_repository')
                    assert set(message.keys()) == EXPECTED_KEYS

                assert len(caplog.records) == 5
                assert result.exit_code == 0


# Sanity check that the hash function maps these similar names to sufficiently dissimilar strings
# From the docs, SequenceMatcher `does not yield minimal edit sequences, but does tend to yield
# matches that "look right" to people. As a rule of thumb, a .ratio() value over 0.6 means the
# sequences are close matches`
# Other than above, 0.4 was picked arbitrarily.
def test_hash_name():
    pipelines = ['pipeline_1', 'pipeline_2', 'pipeline_3']
    hashes = [hash_name(p) for p in pipelines]
    for h in hashes:
        assert len(h) == 64

    assert SequenceMatcher(None, hashes[0], hashes[1]).ratio() < 0.4
    assert SequenceMatcher(None, hashes[0], hashes[2]).ratio() < 0.4
    assert SequenceMatcher(None, hashes[1], hashes[2]).ratio() < 0.4
