#
# diffoscope: in-depth comparison of files, archives, and directories
#
# Copyright © 2017-2020, 2022-2023 Chris Lamb <lamby@debian.org>
#
# diffoscope is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# diffoscope is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.

import os
import re
import pytest

from diffoscope.main import main
from diffoscope.readers import load_diff_from_path
from diffoscope.presenters.utils import (
    create_limited_print_func,
    PrintLimitReached,
    PartialString,
)
from diffoscope.presenters.json import JSONPresenter

from .utils import diff_expand
from .utils.data import cwd_data, data, get_data
from .utils.tools import (
    skip_unless_tools_exist,
    skip_unless_file_version_is_at_least,
)

re_html = re.compile(
    r'.*<body(?P<body>.*)<div class="footer">', re.MULTILINE | re.DOTALL
)


def run(capsys, *args, pair=("test1.tar", "test2.tar")):
    with pytest.raises(SystemExit) as exc, cwd_data():
        main(args + pair)
    out, err = capsys.readouterr()

    assert err == ""
    assert exc.value.code == 1
    return out


def run_images(capsys, *args):
    return run(capsys, *args, pair=("test1.png", "test2.png"))


def extract_body(val):
    """
    Extract the salient parts of HTML fixtures that won't change between
    versions, etc.
    """

    result = re_html.search(val).group("body")

    # Ensure that we extracted something
    assert len(result) > 0

    return result


def expand_collapsed_json(tmpdir, name):
    diff = load_diff_from_path(data(name + ".collapsed-diff.json"))
    diff_path = str(tmpdir.join(name + ".diff.json"))
    with open(diff_path, "w") as fp:
        JSONPresenter(lambda x: print(x, file=fp)).start(
            diff.fmap(diff_expand)
        )
    return diff_path


def test_text_option_is_default(capsys):
    out = run(capsys)

    assert out == get_data("output.txt")


@skip_unless_file_version_is_at_least("5.44")
def test_text_proper_indentation(capsys):
    out = run(capsys, pair=("archive1.tar", "archive2.tar"))

    assert out == get_data("archive12.diff.txt")


def test_text_option_color(capsys):
    out = run(capsys, "--text-color=always")

    assert out == get_data("output.colored.txt")


def test_text_option_with_file(tmpdir, capsys):
    report_path = str(tmpdir.join("report.txt"))

    out = run(capsys, "--text", report_path)

    assert out == ""

    with open(report_path, "r", encoding="utf-8") as f:
        assert f.read() == get_data("output.txt")


def test_text_option_with_stdout(capsys):
    out = run(capsys, "--text", "-")

    assert out == get_data("output.txt")


def test_markdown(capsys):
    out = run(capsys, "--markdown", "-")

    assert out == get_data("output.md")


def test_restructuredtext(capsys):
    out = run(capsys, "--restructured-text", "-")

    assert out == get_data("output.rst")


def test_json(capsys):
    out = run(capsys, "--json", "-")

    assert out == get_data("output.json")


def test_no_report_option(capsys):
    out = run(capsys)

    assert out == get_data("output.txt")


def test_html_option_with_file(tmpdir, capsys):
    report_path = str(tmpdir.join("report.html"))

    out = run(capsys, "--html", report_path)

    assert out == ""
    with open(report_path, "r", encoding="utf-8") as f:
        body = extract_body(f.read())
        assert body.count('div class="difference"') == 4


@skip_unless_tools_exist("compare", "convert", "sng")
def test_html_visuals(tmpdir, capsys):
    report_path = str(tmpdir.join("report.html"))

    out = run_images(capsys, "--html", report_path)

    assert out == ""
    body = extract_body(open(report_path, "r", encoding="utf-8").read())
    assert '<img src="data:image/png;base64' in body
    assert '<img src="data:image/gif;base64' in body


def test_htmldir_option(tmpdir, capsys):
    html_dir = os.path.join(str(tmpdir), "target")

    out = run(capsys, "--html-dir", html_dir, "--jquery", "disable")

    assert out == ""
    assert os.path.isdir(html_dir)
    with open(
        os.path.join(html_dir, "index.html"), "r", encoding="utf-8"
    ) as f:
        body = extract_body(f.read())
        assert body.count('div class="difference"') == 4


def test_html_option_with_stdout(capsys):
    body = extract_body(run(capsys, "--html", "-"))

    assert body.count('div class="difference"') == 4


def test_html_regression_875281(tmpdir, capsys):
    diff_path = expand_collapsed_json(tmpdir, "debian-bug-875281")
    report_path = str(tmpdir.join("report.html"))
    out = run(
        capsys,
        "--html",
        report_path,
        "--max-page-size=5000",
        f"--load-existing-diff={diff_path}",
    )
    assert out == ""

    with open(report_path, "r", encoding="utf-8") as f:
        assert extract_body(f.read()) == get_data(
            "output_regression_875281.html"
        )


def test_html_presentation_issue_363(tmpdir, capsys):
    filename = "salsa-issue-363.diff.json"
    diff_data = get_data(filename)
    diff_path = str(tmpdir.join(filename))
    with open(diff_path, "w") as fp:
        fp.write(diff_data)
    report_path = str(tmpdir.join("report.html"))
    out = run(
        capsys,
        "--html",
        report_path,
        f"--load-existing-diff={diff_path}",
    )
    assert out == ""

    with open(report_path, "r", encoding="utf-8") as f:
        assert extract_body(f.read()) == get_data("output_expected_363.html")


def test_html_presentation_issue_372(tmpdir, capsys):
    filename = "salsa-issue-372.diff.json"
    diff_data = get_data(filename)
    diff_path = str(tmpdir.join(filename))
    with open(diff_path, "w") as fp:
        fp.write(diff_data)
    report_path = str(tmpdir.join("report.html"))
    out = run(
        capsys,
        "--html",
        report_path,
        f"--load-existing-diff={diff_path}",
    )
    assert out == ""

    with open(report_path, "r", encoding="utf-8") as f:
        assert extract_body(f.read()) == get_data("output_expected_372.html")


def test_limited_print():
    def fake(x):
        return None

    with pytest.raises(PrintLimitReached):
        p = create_limited_print_func(fake, 5)
        p("123456")
    with pytest.raises(PrintLimitReached):
        p = create_limited_print_func(fake, 5)
        p("123")
        p("456")
    p = create_limited_print_func(fake, 5)
    p("123")
    p("456", force=True)


def test_partial_string():
    a, b = object(), object()
    tmpl = PartialString("{0} {1}", a, b)
    assert tmpl.holes == (a, b)
    assert tmpl.format({a: "Hello,", b: "World!"}) == "Hello, World!"
    assert tmpl.pformat({a: "Hello,"}) == PartialString("Hello, {0}", b)
    assert tmpl.pformat({b: "World!"}) == PartialString("{0} World!", a)
    assert tmpl.base_len, tmpl.num_holes == (1, 2)
    assert tmpl.size(hole_size=33) == 67
    assert tmpl.pformat({a: PartialString("{0}", b)}) == PartialString(
        "{0} {0}", b
    )
    assert tmpl.pformat({a: tmpl}) == PartialString("{0} {1} {1}", a, b)
    assert tmpl.pformat({b: tmpl}) == PartialString("{0} {0} {1}", a, b)
    PartialString("{1}", a, b)
    with pytest.raises(IndexError):
        PartialString("{0} {1} {2}", a, b)


def test_partial_string_cont():
    t, cont = PartialString.cont()
    t = cont(t, "x: {0}\ny: {1}\n{-1}", object(), object())
    t = cont(t, "z: {0}\n{-1}", object())
    t = cont(t, "")
    key = t.holes
    assert (
        t.format({key[0]: "line1", key[1]: "line2", key[2]: "line3"})
        == "x: line1\ny: line2\nz: line3\n"
    )
    assert t.size(hole_size=5) == 27


def test_partial_string_numl():
    tmpl = PartialString.numl("{0} {1} {2}", 2, object())
    assert tmpl.holes[:2] == (0, 1)
    assert tmpl.pformatl("(1)", "(2)", "(o)") == PartialString("(1) (2) (o)")


def test_partial_string_escape():
    tmpl = PartialString.numl(
        "find {0} -name {1} " + PartialString.escape(r"-exec ls -la {} \;"), 2
    )
    assert tmpl == PartialString(
        "find {0} -name {1} -exec ls -la {{}} \\;", *tmpl.holes
    )
    assert tmpl.size() == 33
    assert tmpl.size(4) == 39
    assert tmpl == PartialString.numl(
        r"find {0} -name {1} -exec ls -la {2} \;", 3
    ).pformat({2: "{}"})

    assert tmpl.pformatl("my{}path", "my{}file") == PartialString(
        "find my{{}}path -name my{{}}file -exec ls -la {{}} \\;"
    )
    assert (
        tmpl.formatl("my{}path", "my{}file")
        == "find my{}path -name my{}file -exec ls -la {} \\;"
    )

    esc = PartialString("{{}} {0}", None)
    assert esc.pformat({None: PartialString.of(None)}) == esc
    assert esc.format({None: "0"}) == "{} 0"
    with pytest.raises(ValueError):
        PartialString("{}")
