import pytest
from nbformat.v4.nbbase import new_notebook, new_markdown_cell, new_code_cell, new_raw_cell
from jupytext.compare import compare
from jupytext.compare import compare_notebooks, NotebookDifference, test_round_trip_conversion as round_trip_conversion


def notebook_metadata():
    return {
        "kernelspec": {
            "display_name": "Python 3",
            "language": "python",
            "name": "python3"
        },
        "language_info": {
            "codemirror_mode": {
                "name": "ipython",
                "version": 3
            },
            "file_extension": ".py",
            "mimetype": "text/x-python",
            "name": "python",
            "nbconvert_exporter": "python",
            "pygments_lexer": "ipython3",
            "version": "3.7.3"
        },
        "toc": {
            "base_numbering": 1,
            "nav_menu": {},
            "number_sections": True,
            "sideBar": True,
            "skip_h1_title": False,
            "title_cell": "Table of Contents",
            "title_sidebar": "Contents",
            "toc_cell": False,
            "toc_position": {},
            "toc_section_display": True,
            "toc_window_display": False
        }
    }


@pytest.fixture()
def notebook_expected():
    return new_notebook(
        metadata=notebook_metadata(),
        cells=[new_markdown_cell('First markdown cell'),
               new_code_cell('1 + 1'),
               new_markdown_cell('Second markdown cell')])


@pytest.fixture()
def notebook_actual():
    metadata = notebook_metadata()
    metadata['language_info']['version'] = '3.6.8'
    return new_notebook(
        metadata=metadata,
        cells=[new_markdown_cell('First markdown cell'),
               new_code_cell('1 + 1'),
               new_markdown_cell('Modified markdown cell')])


def test_compare_on_notebooks(notebook_actual, notebook_expected):
    with pytest.raises(AssertionError) as err:
        compare(notebook_actual, notebook_expected)

    assert str(err.value) == """
--- expected
+++ actual
@@ -15,7 +15,7 @@
   {
    "cell_type": "markdown",
    "metadata": {},
-   "source": "Second markdown cell"
+   "source": "Modified markdown cell"
   }
  ],
  "metadata": {
@@ -34,7 +34,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.7.3"
+   "version": "3.6.8"
   },
   "toc": {
    "base_numbering": 1,"""


def test_raise_on_different_metadata():
    ref = new_notebook(metadata={'kernelspec': {'language': 'python', 'name': 'python', 'display_name': 'Python'}},
                       cells=[new_markdown_cell('Cell one')])
    test = new_notebook(metadata={'kernelspec': {'language': 'R', 'name': 'R', 'display_name': 'R'}},
                        cells=[new_markdown_cell('Cell one')])
    with pytest.raises(NotebookDifference):
        compare_notebooks(test, ref, 'md')


@pytest.mark.parametrize('raise_on_first_difference', [True, False])
def test_raise_on_different_cell_type(raise_on_first_difference):
    ref = new_notebook(cells=[new_markdown_cell('Cell one'), new_code_cell('Cell two')])
    test = new_notebook(cells=[new_markdown_cell('Cell one'), new_raw_cell('Cell two')])
    with pytest.raises(NotebookDifference):
        compare_notebooks(test, ref, 'md', raise_on_first_difference=raise_on_first_difference)


@pytest.mark.parametrize('raise_on_first_difference', [True, False])
def test_raise_on_different_cell_content(raise_on_first_difference):
    ref = new_notebook(cells=[new_markdown_cell('Cell one'), new_code_cell('Cell two')])
    test = new_notebook(cells=[new_markdown_cell('Cell one'), new_code_cell('Modified cell two')])
    with pytest.raises(NotebookDifference):
        compare_notebooks(test, ref, 'md', raise_on_first_difference=raise_on_first_difference)


def test_raise_on_incomplete_markdown_cell():
    ref = new_notebook(cells=[new_markdown_cell('Cell one\n\n\nsecond line')])
    test = new_notebook(cells=[new_markdown_cell('Cell one')])
    with pytest.raises(NotebookDifference):
        compare_notebooks(test, ref, 'md')


def test_does_raise_on_split_markdown_cell():
    ref = new_notebook(cells=[new_markdown_cell('Cell one\n\n\nsecond line')])
    test = new_notebook(cells=[new_markdown_cell('Cell one'),
                               new_markdown_cell('second line')])
    with pytest.raises(NotebookDifference):
        compare_notebooks(test, ref, 'md')


def test_raise_on_different_cell_metadata():
    ref = new_notebook(cells=[new_code_cell('1+1')])
    test = new_notebook(cells=[new_code_cell('1+1', metadata={'metakey': 'value'})])
    with pytest.raises(NotebookDifference):
        compare_notebooks(test, ref, 'py:light')


@pytest.mark.parametrize('raise_on_first_difference', [True, False])
def test_raise_on_different_cell_count(raise_on_first_difference):
    ref = new_notebook(cells=[new_code_cell('1')])
    test = new_notebook(cells=[new_code_cell('1'),
                               new_code_cell('2')])
    with pytest.raises(NotebookDifference):
        compare_notebooks(test, ref, 'py:light', raise_on_first_difference=raise_on_first_difference)

    with pytest.raises(NotebookDifference):
        compare_notebooks(ref, test, 'py:light', raise_on_first_difference=raise_on_first_difference)


def test_does_not_raise_on_blank_line_removed():
    ref = new_notebook(cells=[new_code_cell('1+1\n    ')])
    test = new_notebook(cells=[new_code_cell('1+1')])
    compare_notebooks(test, ref, 'py:light')


def test_strict_raise_on_blank_line_removed():
    ref = new_notebook(cells=[new_code_cell('1+1\n')])
    test = new_notebook(cells=[new_code_cell('1+1')])
    with pytest.raises(NotebookDifference):
        compare_notebooks(test, ref, 'py:light', allow_expected_differences=False)


def test_dont_raise_on_different_outputs():
    ref = new_notebook(cells=[new_code_cell('1+1')])
    test = new_notebook(cells=[new_code_cell('1+1', outputs=[
        {
            "data": {
                "text/plain": [
                    "2"
                ]
            },
            "execution_count": 1,
            "metadata": {},
            "output_type": "execute_result"
        }
    ])])
    compare_notebooks(test, ref, 'md')


@pytest.mark.parametrize('raise_on_first_difference', [True, False])
def test_raise_on_different_outputs(raise_on_first_difference):
    ref = new_notebook(cells=[new_code_cell('1+1')])
    test = new_notebook(cells=[new_code_cell('1+1', outputs=[
        {
            "data": {
                "text/plain": [
                    "2"
                ]
            },
            "execution_count": 1,
            "metadata": {},
            "output_type": "execute_result"
        }
    ])])
    with pytest.raises(NotebookDifference):
        compare_notebooks(test, ref, 'md', raise_on_first_difference=raise_on_first_difference, compare_outputs=True)


def test_test_round_trip_conversion():
    notebook = new_notebook(cells=[new_code_cell('1+1', outputs=[
        {
            "data": {
                "text/plain": [
                    "2"
                ]
            },
            "execution_count": 1,
            "metadata": {},
            "output_type": "execute_result"
        }
    ])], metadata={'main_language': 'python'})

    round_trip_conversion(notebook, {'extension': '.py'}, update=True)


def test_mutiple_cells_differ():
    nb1 = new_notebook(cells=[new_code_cell(''),
                              new_code_cell('2')])
    nb2 = new_notebook(cells=[new_code_cell('1+1'),
                              new_code_cell('2\n2')])
    with pytest.raises(NotebookDifference) as exception_info:
        compare_notebooks(nb2, nb1, raise_on_first_difference=False)
    assert 'Cells 1,2 differ' in exception_info.value.args[0]


def test_cell_metadata_differ():
    nb1 = new_notebook(cells=[new_code_cell('1'),
                              new_code_cell('2', metadata={'additional': 'metadata1'})])
    nb2 = new_notebook(cells=[new_code_cell('1'),
                              new_code_cell('2', metadata={'additional': 'metadata2'})])
    with pytest.raises(NotebookDifference) as exception_info:
        compare_notebooks(nb2, nb1, raise_on_first_difference=False)
    assert "Cell metadata 'additional' differ" in exception_info.value.args[0]


def test_notebook_metadata_differ():
    nb1 = new_notebook(cells=[new_code_cell('1'),
                              new_code_cell('2')])
    nb2 = new_notebook(cells=[new_code_cell('1'),
                              new_code_cell('2')],
                       metadata={'kernelspec': {'language': 'python', 'name': 'python', 'display_name': 'Python'}})
    with pytest.raises(NotebookDifference) as exception_info:
        compare_notebooks(nb2, nb1, raise_on_first_difference=False)
    assert "Notebook metadata differ" in exception_info.value.args[0]
