#!/usr/bin/env python3
# Copyright: 2026, 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.

"""Non-redundant, non-flaky module-structure checks."""

import importlib
import sys
import unittest

import debian.copyright as dc

from decopy.dep5doc.copyright_contracts import CopyrightProtocol
from decopy.dep5doc.dep5 import Copyright, Group, read_copyright
from decopy.grouping.group_contracts import GroupProtocol, LicenseKeyed
from decopy.licensing.holders import CopyrightHolders

_ALL_MODULES = [
    "decopy.cmdoptions",
    "decopy.common.core_types",
    "decopy.dep5doc.dep5",
    "decopy.dep5doc.copyright_reader",
    "decopy.grouping.grouping",
    "decopy.grouping.grouping_strategy",
    "decopy.licensing.holders",
    "decopy.licensing.license_parsers",
    "decopy.licensing.license_subs",
    "decopy.licensing.licenses",
    "decopy.fstree.nodes",
    "decopy.fstree.vcs",
    "decopy.dep5doc.output",
    "decopy.common.re_types",
    "decopy.dep5doc.renderer",
    "decopy.licensing.res",
    "decopy.fstree.tree",
]


class TestImportOrder(unittest.TestCase):
    """Modules can be imported in any order without circular-import errors."""

    def setUp(self):
        # Snapshot every loaded decopy.* module so re-importing below cannot
        # leak a second copy of a class (e.g. a fresh nodes.DirInfo) into later
        # tests, which would break isinstance checks across module boundaries.
        self._saved_modules = {
            name: module
            for name, module in sys.modules.items()
            if name == "decopy" or name.startswith("decopy.")
        }

    def tearDown(self):
        for name in [
            n for n in sys.modules if n == "decopy" or n.startswith("decopy.")
        ]:
            if name not in self._saved_modules:
                del sys.modules[name]
        sys.modules.update(self._saved_modules)

    def _reimport_all(self, order):
        for name in _ALL_MODULES:
            sys.modules.pop(name, None)
        for name in order:
            importlib.import_module(name)

    def test_forward_order(self):
        self._reimport_all(_ALL_MODULES)

    def test_reverse_order(self):
        self._reimport_all(list(reversed(_ALL_MODULES)))

    def test_sorted_order(self):
        self._reimport_all(sorted(_ALL_MODULES))


class TestCopyrightProtocolImplementation(unittest.TestCase):
    """Copyright implements CopyrightProtocol and dc.Copyright."""

    def setUp(self):
        self.copyright = Copyright()

    def test_implements_copyright_protocol(self):
        self.assertIsInstance(self.copyright, CopyrightProtocol)

    def test_protocol_requires_rendering_members(self):
        """The rendering path reads copyright_.header and .registry, so the
        protocol must require them, not just the processing methods."""

        class WithoutRenderingMembers:
            groups = None

            @staticmethod
            def build(filetree, options, registry=None): ...
            def process(self, filetree): ...
            def remove_misplaced_files(self, options, assigned=None): ...
            def get_group_dict(self, options):
                return {}

        self.assertNotIsInstance(WithoutRenderingMembers(), CopyrightProtocol)

    def test_composes_not_inherits_dc_copyright(self):
        # Copyright holds a dc.Copyright by composition; it is not one. The
        # header (a dc.Header) is exposed by delegation.
        self.assertNotIsInstance(self.copyright, dc.Copyright)
        self.assertIsInstance(self.copyright.header, dc.Header)

    def test_groups_initially_none(self):
        self.assertIsNone(self.copyright.groups)

    def test_reader_builds_copyright(self):
        # Reading an existing file is the reader's job, not the class's; the
        # class no longer carries a build method.
        self.assertTrue(callable(read_copyright))
        self.assertFalse(hasattr(Copyright, "build"))

    def test_has_process_method(self):
        self.assertTrue(callable(self.copyright.process))

    def test_has_remove_misplaced_files_method(self):
        self.assertTrue(callable(self.copyright.remove_misplaced_files))

    def test_has_get_group_dict_method(self):
        self.assertTrue(callable(self.copyright.get_group_dict))


class TestGroupProtocolImplementation(unittest.TestCase):
    """Group implements GroupProtocol with expected attribute types."""

    def setUp(self):
        self.group = Group(position=0)

    def test_implements_group_protocol(self):
        self.assertIsInstance(self.group, GroupProtocol)

    def test_license_is_string(self):
        self.assertIsInstance(self.group.license, str)

    def test_key_initially_none(self):
        self.assertIsNone(self.group.key)

    def test_position_is_int(self):
        self.assertEqual(self.group.position, 0)

    def test_copyrights_is_copyright_group(self):
        self.assertIsInstance(self.group.copyrights, CopyrightHolders)

    def test_licenses_is_dict(self):
        self.assertIsInstance(self.group.licenses, dict)

    def test_license_filenames_is_set(self):
        self.assertIsInstance(self.group.license_filenames, set)

    def test_comments_is_list(self):
        self.assertIsInstance(self.group.comments, list)

    def test_stored_initially_none(self):
        self.assertIsNone(self.group.stored)

    def test_group_protocol_has_get_license_key(self):
        self.assertTrue(hasattr(GroupProtocol, "get_license_key"))

    def test_implements_license_keyed(self):
        self.assertIsInstance(self.group, LicenseKeyed)

    def test_license_keyed_has_get_license_key(self):
        self.assertTrue(hasattr(LicenseKeyed, "get_license_key"))

    def test_get_license_key_returns_string(self):
        result = self.group.get_license_key()
        self.assertIsInstance(result, str)


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