import os
import json
import pytest
import pyct.cmd
from nbsite.cmd import generate_rst, build

# Note: a lot of this setup is copied from the new (2018-11-01) test in
# pyct. Potentially this could be consolidated at some point. The fixture
# setup could also move to conftest.py

# Use `pytest  --pdb -m "not slow"` to run just the faster rst generating tests

DATA_FILE_0_CONTENT = u"""
name,score,rank
Alice,100.5,1
Bob,50.3,2
Charlie,25,3
"""

DATA_FILE_1_CONTENT = u"""
name,score,rank
Alice,100.5,1
Bob,50.3,2
Charlie,25,3
Dave,28,4
Eve,25,3
Frank,75,9
"""

EXAMPLE_0_CONTENT = u"""{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**NOTE:** This is a temporary notebook that gets created for tests."
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
"""

EXAMPLE_1_CONTENT = u"""{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**NOTE:** This is another temporary notebook that gets created for tests."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here is a ref to another notebook with the [right number](0_Zeroth_Notebook.ipynb)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here is a ref to it with the [wrong number](1_Zeroth_Notebook.ipynb)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here is a ref to it with [no number](Zeroth_Notebook.ipynb)."
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
"""

APPENDIX_0_CONTENT = u"""{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('first cell')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import foo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('last cell')"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
"""

CONF_CONTENT = u"""
from nbsite.shared_conf import *

"""

INDEX_CONTENT = u"""
*******
Project
*******

.. toctree::
   :hidden:
   :maxdepth: 2

   Zeroth Notebook <Zeroth_Notebook>
   First Notebook <First_Notebook>
   Appendix 0 <Appendix_0>
"""

EXAMPLE_0_RST = u"""
***************
Zeroth Notebook
***************

.. notebook:: test_project ../examples/0_Zeroth_Notebook.ipynb
    :offset: 0
"""

EXAMPLE_1_RST = u"""
**************
First Notebook
**************

.. notebook:: test_project ../examples/1_First_Notebook.ipynb
    :offset: 0
"""

APPENDIX_0_RST = u"""
**********
Appendix 0
**********

.. notebook:: test_project ../examples/Appendix_0.ipynb
    :offset: 0
"""

@pytest.fixture(autouse=True)
def tmp_module(tmp_path):
    """This sets up a temporary directory structure meant to mimic a module
    """
    project = tmp_path / "static_module"
    project.mkdir()
    (project / "examples").mkdir()
    (project / "examples" / "0_Zeroth_Notebook.ipynb").write_text(EXAMPLE_0_CONTENT)
    (project / "examples" / "1_First_Notebook.ipynb").write_text(EXAMPLE_1_CONTENT)
    (project / "examples" / "Appendix_0.ipynb").write_text(APPENDIX_0_CONTENT)
    (project / "examples" / "data").mkdir()
    (project / "examples" / "data" / "data_0.csv").write_text(DATA_FILE_0_CONTENT)
    (project / "examples" / "data" / "data_1.csv").write_text(DATA_FILE_1_CONTENT)
    return project

@pytest.fixture(autouse=True)
def monkeypatch_find_examples(monkeypatch, tmp_module):
    """Monkeypatching find examples to use a tmp examples.
    """
    def _find_examples(name):
        return os.path.join(str(tmp_module), "examples")
    monkeypatch.setattr(pyct.cmd, '_find_examples', _find_examples)

@pytest.fixture(scope='function')
def tmp_project(tmp_path):
    """
    This sets up a temporary directory structure meant to mimic the project.

    All test output should write to this project. and tmp_module should
    be treated as immutable. Since this is function-scoped, every test
    will have a clean slate.
    """
    project = tmp_path / "test_project"
    project.mkdir()
    path = str(project / "examples")
    pyct.cmd.examples(name="nbsite", path=path)
    return project

@pytest.fixture(scope='function')
def tmp_project_with_docs_skeleton(tmp_project):
    project = tmp_project
    (project / "doc").mkdir()
    (project / "doc" / "conf.py").write_text(CONF_CONTENT)
    (project / "doc" / "index.rst").write_text(INDEX_CONTENT)
    return project


def test_generate_rst(tmp_project):
    project = tmp_project

    generate_rst("test_project", project_root=str(project))
    assert (project / "doc" / "Zeroth_Notebook.rst").is_file()
    assert (project / "doc" / "First_Notebook.rst").is_file()
    assert (project / "doc" / "Appendix_0.rst").is_file()

def test_generate_rst_with_skip_one_notebook(tmp_project):
    project = tmp_project
    generate_rst("test_project", project_root=str(project), skip='.*0_.*')
    assert not (project / "doc" / "Zeroth_Notebook.rst").is_file()
    assert (project / "doc" / "First_Notebook.rst").is_file()

def test_generate_rst_with_skip_list_of_notebooks(tmp_project):
    project = tmp_project
    generate_rst("test_project", project_root=str(project), skip='.*0_.*, .*1_.*')
    assert not (project / "doc" / "Zeroth_Notebook.rst").is_file()
    assert not (project / "doc" / "First_Notebook.rst").is_file()

def test_generate_rst_with_skip_glob_matching_both_notebooks(tmp_project):
    project = tmp_project
    generate_rst("test_project", project_root=str(project), skip='.*Notebook.*')
    assert not (project / "doc" / "Zeroth_Notebook.rst").is_file()
    assert not (project / "doc" / "First_Notebook.rst").is_file()

def test_generate_rst_with_skip_glob_matching_both_notebooks_undercase(tmp_project):
    project = tmp_project
    generate_rst("test_project", project_root=str(project), skip='.*notebook.*')
    assert not (project / "doc" / "Zeroth_Notebook.rst").is_file()
    assert not (project / "doc" / "First_Notebook.rst").is_file()


def test_generate_rst_with_keep_numbers(tmp_project):

    expected_index_toc = (
        '    Introduction <self>\n'
        '    0 Zeroth Notebook <0_Zeroth_Notebook>\n'
        '    1 First Notebook <1_First_Notebook>\n'
        '    Appendix 0 <Appendix_0>')

    project = tmp_project
    (project / "examples" / "index.ipynb").write_text(EXAMPLE_0_CONTENT)
    generate_rst("test_project", project_root=str(project), keep_numbers=True)
    assert (project / "doc" / "index.rst").is_file()
    assert expected_index_toc in (project / "doc" / "index.rst").read_text()
    assert (project / "doc" / "0_Zeroth_Notebook.rst").is_file()
    assert (project / "doc" / "1_First_Notebook.rst").is_file()

def test_generate_rst_with_strip_numbers_is_default(tmp_project):

    expected_index_toc = (
        '    Introduction <self>\n'
        '    Zeroth Notebook <Zeroth_Notebook>\n'
        '    First Notebook <First_Notebook>\n'
        '    Appendix 0 <Appendix_0>')

    project = tmp_project
    (project / "examples" / "index.ipynb").write_text(EXAMPLE_0_CONTENT)
    generate_rst("test_project", project_root=str(project))
    assert (project / "doc" / "index.rst").is_file()
    actual_index = (project / "doc" / "index.rst").read_text()
    assert expected_index_toc in actual_index
    assert (project / "doc" / "Zeroth_Notebook.rst").is_file()
    assert (project / "doc" / "First_Notebook.rst").is_file()

def test_generate_rst_with_nblink_as_none(tmp_project):

    expected = ['**************',
                'First Notebook',
                '**************',
                '', '.. notebook:: test_project ../examples/1_First_Notebook.ipynb',
                '    :offset: 0']
    project = tmp_project
    generate_rst("test_project", project_root=str(project), nblink='none',
                 host='GitHub', org='pyviz', repo='nbsite', branch='master')
    rstpath = (project / "doc" / "First_Notebook.rst")
    assert rstpath.is_file()
    with open(rstpath, 'r') as f:
        contents = f.read().splitlines()
        assert  contents[5:] == expected

def test_generate_rst_with_nblink_top(tmp_project):

    expected = ['**************',
                'First Notebook',
                '**************',
                '',
                '`Right click to download this notebook from GitHub. <https://raw.githubusercontent.com/pyviz/nbsite/master/examples/1_First_Notebook.ipynb>`_',
                '', '', '-------', '',
                '.. notebook:: test_project ../examples/1_First_Notebook.ipynb',
                '    :offset: 0']

    project = tmp_project
    generate_rst("test_project", project_root=str(project), nblink='top',
                 host='GitHub', org='pyviz', repo='nbsite', branch='master')
    rstpath = (project / "doc" / "First_Notebook.rst")
    assert rstpath.is_file()
    with open(rstpath, 'r') as f:
        contents = f.read().splitlines()
        assert  contents[5:] == expected

def test_generate_rst_with_nblink_both(tmp_project):

    expected = ['**************',
                'First Notebook',
                '**************',
                '',
                '`Right click to download this notebook from GitHub. <https://raw.githubusercontent.com/pyviz/nbsite/master/examples/1_First_Notebook.ipynb>`_',
                '', '', '-------', '',
                '.. notebook:: test_project ../examples/1_First_Notebook.ipynb',
                '    :offset: 0',
                '', '', '-------', '',
                '`Right click to download this notebook from GitHub. <https://raw.githubusercontent.com/pyviz/nbsite/master/examples/1_First_Notebook.ipynb>`_']

    project = tmp_project
    generate_rst("test_project", project_root=str(project), nblink='both',
                 host='GitHub', org='pyviz', repo='nbsite', branch='master')
    rstpath = (project / "doc" / "First_Notebook.rst")
    assert rstpath.is_file()
    with open(rstpath, 'r') as f:
        contents = f.read().splitlines()
        assert  contents[5:] == expected

def test_generate_rst_with_nblink_bottom(tmp_project):

    expected = ['**************',
                'First Notebook',
                '**************',
                '',
                '.. notebook:: test_project ../examples/1_First_Notebook.ipynb',
                '    :offset: 0',
                '', '', '-------', '',
                '`Right click to download this notebook from GitHub. <https://raw.githubusercontent.com/pyviz/nbsite/master/examples/1_First_Notebook.ipynb>`_']

    project = tmp_project
    generate_rst("test_project", project_root=str(project), nblink='bottom',
                 host='GitHub', org='pyviz', repo='nbsite', branch='master')
    rstpath = (project / "doc" / "First_Notebook.rst")
    assert rstpath.is_file()
    with open(rstpath, 'r') as f:
        contents = f.read().splitlines()
        assert  contents[5:] == expected

def test_generate_rst_with_no_nblink_set_defaults_to_bottom(tmp_project):
    expected = ['**************',
                'First Notebook',
                '**************',
                '',
                '.. notebook:: test_project ../examples/1_First_Notebook.ipynb',
                '    :offset: 0',
                '', '', '-------', '',
                '`Right click to download this notebook from GitHub. <https://raw.githubusercontent.com/pyviz/nbsite/master/examples/1_First_Notebook.ipynb>`_']

    project = tmp_project
    generate_rst("test_project", project_root=str(project),
                 host='GitHub', org='pyviz', repo='nbsite', branch='master')
    rstpath = (project / "doc" / "First_Notebook.rst")
    assert rstpath.is_file()
    with open(rstpath, 'r') as f:
        contents = f.read().splitlines()
        assert  contents[5:] == expected

#### Don't need to do much testing of build it depends on sphinx
@pytest.mark.slow
def test_build(tmp_project_with_docs_skeleton):
    project = tmp_project_with_docs_skeleton
    (project / "doc" / "Zeroth_Notebook.rst").write_text(EXAMPLE_0_RST)
    (project / "doc" / "First_Notebook.rst").write_text(EXAMPLE_1_RST)
    build('html', str(project / "builtdocs"), project_root=str(project), examples_assets='')
    assert (project / "doc" / "0_Zeroth_Notebook.ipynb").is_file()
    assert (project / "doc" / "1_First_Notebook.ipynb").is_file()
    assert (project / "builtdocs" / "Zeroth_Notebook.html").is_file()
    assert (project / "builtdocs" / "First_Notebook.html").is_file()

@pytest.mark.slow
def test_build_with_nblink_at_top_succeeds(tmp_project_with_docs_skeleton):
    project = tmp_project_with_docs_skeleton
    generate_rst("test_project", project_root=str(project), nblink='top',
                 host='GitHub', org='pyviz', repo='nbsite', branch='master')
    rstpath = (project / "doc" / "First_Notebook.rst")
    assert rstpath.is_file()
    build('html', str(project / "builtdocs"), project_root=str(project), examples_assets='')
    assert (project / "builtdocs" / "First_Notebook.html").is_file()
    html = (project / "builtdocs" / "First_Notebook.html").read_text()
    assert 'This is another temporary notebook that gets created for tests' in html, \
           "The notebook did not get build to html properly - look for sphinx warnings and errors"

@pytest.mark.slow
def test_build_with_just_one_rst(tmp_project_with_docs_skeleton):
    project = tmp_project_with_docs_skeleton
    (project / "doc" / "Zeroth_Notebook.rst").write_text(EXAMPLE_0_RST)
    build('html', str(project / "builtdocs"), project_root=str(project), examples_assets='')
    assert (project / "builtdocs" / "Zeroth_Notebook.html").is_file()
    assert not (project / "builtdocs" / "First_Notebook.html").is_file()
    assert (project / "builtdocs" / ".nojekyll").is_file()

@pytest.mark.slow
def test_build_deletes_by_default(tmp_project_with_docs_skeleton):
    project = tmp_project_with_docs_skeleton
    (project / "doc" / "Zeroth_Notebook.rst").write_text(EXAMPLE_0_RST)
    (project / "doc" / "First_Notebook.rst").write_text(EXAMPLE_1_RST)
    build('html', str(project / "builtdocs"), project_root=str(project), examples_assets='')
    assert not (project / "builtdocs" / ".doctrees").is_dir()
    assert (project / "builtdocs" / "First_Notebook.html").is_file()
    assert len(list((project / "builtdocs").iterdir())) == 10

@pytest.mark.slow
def test_build_with_clean_dry_run_does_not_delete(tmp_project_with_docs_skeleton):
    project = tmp_project_with_docs_skeleton
    (project / "doc" / "Zeroth_Notebook.rst").write_text(EXAMPLE_0_RST)
    (project / "doc" / "First_Notebook.rst").write_text(EXAMPLE_1_RST)
    build('html', str(project / "builtdocs"), project_root=str(project), examples_assets='', clean_dry_run=True)
    assert (project / "builtdocs" / ".doctrees").is_dir()
    assert (project / "builtdocs" / "First_Notebook.html").is_file()
    assert len(list((project / "builtdocs").iterdir())) == 12

@pytest.mark.slow
def test_build_copies_json(tmp_project_with_docs_skeleton):
    project = tmp_project_with_docs_skeleton
    (project / "doc" / "Zeroth_Notebook.rst").write_text(EXAMPLE_0_RST)
    (project / "doc" / "example_json_blob.json").write_text("some json")
    (project / "doc" / "topics").mkdir()
    (project / "doc" / "topics" / "nested_example_json_blob.json").write_text("some json")
    build('html', str(project / "builtdocs"), project_root=str(project), examples_assets='')
    assert (project / "builtdocs" / "example_json_blob.json").is_file()
    assert (project / "builtdocs" / "topics").is_dir()
    assert (project / "builtdocs" / "topics" / "nested_example_json_blob.json").is_file()

@pytest.mark.slow
def test_build_with_error_output(tmp_project_with_docs_skeleton):
    project = tmp_project_with_docs_skeleton
    (project / "doc" / "Zeroth_Notebook.rst").write_text(EXAMPLE_0_RST)
    (project / "doc" / "First_Notebook.rst").write_text(EXAMPLE_1_RST)
    (project / "doc" / "Appendix_0.rst").write_text(APPENDIX_0_RST)
    assert not (project / "doc" / "Appendix_0.ipynb").is_file()
    build('html', str(project / "builtdocs"), project_root=str(project), examples_assets='')
    assert (project / "doc" / "1_First_Notebook.ipynb").is_file()
    assert (project / "doc" / "Appendix_0.ipynb").is_file()
    nb = json.loads((project / "doc" / "Appendix_0.ipynb").read_text())
    assert nb['cells'][1]['outputs'][0]['ename'] == 'ModuleNotFoundError'
    assert len(nb['cells'][2]['outputs']) == 0

@pytest.mark.slow
def test_build_with_fixes_links(tmp_project):
    project = tmp_project
    (project / "doc").mkdir()
    (project / "doc" / "conf.py").write_text(CONF_CONTENT)
    (project / "examples" / "index.ipynb").write_text(EXAMPLE_0_CONTENT)
    generate_rst("test_project", project_root=str(project))
    build('html', str(project / "builtdocs"), project_root=str(project), examples_assets='')
    assert (project / "doc" / "1_First_Notebook.ipynb").is_file()
    assert (project / "builtdocs" / "First_Notebook.html").is_file()
    html = (project / "builtdocs" / "First_Notebook.html").read_text()
    assert '<a href="Zeroth_Notebook.html">right number</a>' in html
    assert '<a href="Zeroth_Notebook.html">wrong number</a>' in html
    assert '<a href="Zeroth_Notebook.html">no number</a>' in html

@pytest.mark.slow
def test_build_with_keep_numbers_passes_even_when_link_target_does_not_exist(tmp_project):
    project = tmp_project
    (project / "doc").mkdir()
    (project / "doc" / "conf.py").write_text(CONF_CONTENT)
    (project / "examples" / "index.ipynb").write_text(EXAMPLE_0_CONTENT)
    generate_rst("test_project", project_root=str(project), keep_numbers=True)
    build('html', str(project / "builtdocs"), project_root=str(project), examples_assets='')
    assert (project / "doc" / "1_First_Notebook.ipynb").is_file()
    assert (project / "builtdocs" / "1_First_Notebook.html").is_file()
    html = (project / "builtdocs" / "1_First_Notebook.html").read_text()
    assert '<a href="0_Zeroth_Notebook.html">right number</a>' in html
    assert '<a href="1_Zeroth_Notebook.html">wrong number</a>' in html
    assert '<a href="Zeroth_Notebook.html">no number</a>' in html
