#!/usr/bin/env python3

# Unit tests for decopy
# Copyright: 2016, Maximiliano Curia <maxy@debian.org>
#
# License: ISC
#  Permission to use, copy, modify, and/or distribute this software for any
#  purpose with or without fee is hereby granted, provided that the above
#  copyright notice and this permission notice appear in all copies.
#  .
#  THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
#  REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
#  AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
#  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
#  LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
#  OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
#  PERFORMANCE OF THIS SOFTWARE.

import contextlib
import io
import sys
import typing as t
import unittest
from unittest.mock import DEFAULT, patch

from decopy import cmdoptions, dep5, nodes, output, tree
from decopy.core_types import CopyrightHolder, License


class TestOutput(unittest.TestCase):
    def setUp(self):
        # Create one root with one dir with one file
        self.tree = tree.RootInfo(root=".")
        self.groups = {}

    def test_empty(self):
        groups: dict = {}
        copyright_ = None
        options = cmdoptions.process_options([])

        with patch.multiple(
            sys,
            stdout=DEFAULT,
            stderr=DEFAULT,
            new_callable=io.StringIO,
        ) as _sys:
            output.generate_output(groups, self.tree, copyright_, options)
        mocks = t.cast(dict, _sys)
        out = mocks["stdout"].getvalue()
        err = mocks["stderr"].getvalue()
        self.assertFalse(len(err), f"Unexpected stderr: {err}")
        self.assertEqual(
            out,
            "Format: https://www.debian.org/doc/"
            "packaging-manuals/copyright-format/1.0/\n"
            "Source: TODO\n",
            f"Unexpected stdout: {out}",
        )

    def test_empty_partial(self):
        groups: dict = {}
        copyright_ = None
        options = cmdoptions.process_options(["--mode=partial", "."])

        with patch.multiple(
            sys,
            stdout=DEFAULT,
            stderr=DEFAULT,
            new_callable=io.StringIO,
        ) as _sys:
            output.generate_output(groups, self.tree, copyright_, options)
        mocks = t.cast(dict, _sys)
        out = mocks["stdout"].getvalue()
        err = mocks["stderr"].getvalue()
        self.assertFalse(len(err), f"Unexpected stderr: {err}")
        self.assertEqual(
            out,
            "Format: https://www.debian.org/doc/"
            "packaging-manuals/copyright-format/1.0/\n"
            "Source: TODO\n"
            "Comment: *** only: . ***\n",
            f"Unexpected stdout: {out}",
        )


_CURRENT_FORMAT = (
    "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n"
)


class TestGenerateOutputDocument(unittest.TestCase):
    """Characterization tests for the full generate_output document.

    These pin the document assembly (header, blank-line separators, file
    paragraphs ordered by sort_key, trailing license stanzas) before the
    Renderer refactor moves that orchestration behind a protocol.
    """

    def setUp(self):
        self._saved_licenses = License.licenses
        License.licenses = {}
        self.tree = tree.RootInfo(root=".")

    def tearDown(self):
        License.licenses = self._saved_licenses

    def _run(self, groups, copyright_, args):
        options = cmdoptions.process_options(args)
        with patch.multiple(
            sys,
            stdout=DEFAULT,
            stderr=DEFAULT,
            new_callable=io.StringIO,
        ) as _sys:
            output.generate_output(groups, self.tree, copyright_, options)
        mocks = t.cast(dict, _sys)
        return (
            mocks["stdout"].getvalue(),
            mocks["stderr"].getvalue(),
        )

    def _two_groups(self, options):
        """An MIT group (two files under src/) and a GPL-2 group (README)."""
        root = nodes.DirInfo(parent=None, name="")
        src = nodes.DirInfo(parent=root, name="src")
        root.file_list["src"] = src
        mit = dep5.Group(position=1)
        for name in ("a.c", "b.c"):
            f = nodes.FileInfo(parent=src, name=name)
            f.add_licenses(["MIT"])
            f.add_copyrights([CopyrightHolder("Alice", "alice@x")])
            mit.add_file(f)
        gpl = dep5.Group(position=0)
        readme = nodes.FileInfo(parent=root, name="README")
        readme.add_licenses(["GPL-2"])
        readme.add_copyrights([CopyrightHolder("Bob", "bob@x")])
        gpl.add_file(readme)
        return {mit.get_key(options): mit, gpl.get_key(options): gpl}

    def test_full_document_no_glob(self):
        args = ["--no-progress", "--root", "tests/testdata/one", "--no-glob"]
        options = cmdoptions.process_options(args)
        out, err = self._run(self._two_groups(options), None, args)
        self.assertEqual(err, "")
        self.assertEqual(
            out,
            _CURRENT_FORMAT + "Source: TODO\n"
            "\n"
            "Files: src/a.c\n"
            "       src/b.c\n"
            "Copyright: Alice <alice@x>\n"
            "License: MIT\n"
            "\n"
            "Files: README\n"
            "Copyright: Bob <bob@x>\n"
            "License: GPL-2\n"
            "\n"
            "License: GPL-2\n"
            "Comment: Add the corresponding license text here\n"
            "\n"
            "License: MIT\n"
            "Comment: Add the corresponding license text here\n",
        )

    def test_glob_collapses_full_subtree_to_star(self):
        args = ["--no-progress", "--root", "tests/testdata/one", "--glob"]
        options = cmdoptions.process_options(args)
        root = nodes.DirInfo(parent=None, name="")
        src = nodes.DirInfo(parent=root, name="src")
        root.file_list["src"] = src
        group = dep5.Group(position=0)
        for name in ("a.c", "b.c"):
            f = nodes.FileInfo(parent=src, name=name)
            f.add_licenses(["MIT"])
            group.add_file(f)
        out, err = self._run({group.get_key(options): group}, None, args)
        self.assertEqual(err, "")
        self.assertIn("Files: *\n", out)
        self.assertNotIn("src/a.c", out)

    def test_empty_copyright_object_renders_todo_source(self):
        out, err = self._run(
            {}, dep5.Copyright(), ["--no-progress", "--root", "tests/testdata/one"]
        )
        self.assertEqual(err, "")
        self.assertEqual(out, _CURRENT_FORMAT + "Source: TODO\n")

    def test_existing_copyright_header_overrides_format_keeps_fields(self):
        text = (
            "Format: https://example.org/old-format\n"
            "Upstream-Name: example\n"
            "Source: https://example.org/dl\n"
        )
        # Build outside the stdout/stderr patch so the loader's "format not
        # known" warning is not captured by the output assertion.
        with contextlib.redirect_stderr(io.StringIO()):
            copyright_ = dep5.Copyright(io.StringIO(text), strict=False)
        out, _ = self._run(
            {}, copyright_, ["--no-progress", "--root", "tests/testdata/one"]
        )
        self.assertEqual(
            out,
            _CURRENT_FORMAT + "Upstream-Name: example\n"
            "Source: https://example.org/dl\n",
        )


if __name__ == "__main__":
    unittest.main()
