#!/usr/bin/env python3

# Characterization tests for the grouping/splitting policy.
# 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.

"""Pin the current grouping policy before the GroupingStrategy refactor.

These tests lock the observable behaviour of the four scattered
group_by/split_* conditionals that we will move behind a strategy:

  * grouping.make_group_key          - the copyright slot decision
  * file_policy.splitting_path       - the split_on_license/split_debian rules
  * copyright_processor.remove_misplaced_files - the group_by copyright fallback

The fourth site, Group.sort_key's split_debian band, is already pinned by
TestGroupSortKey in dep5_test.py, so it is not duplicated here.
"""

import unittest

from decopy import cmdoptions, dep5, file_policy, nodes
from decopy.core_types import CopyrightHolder
from decopy.grouping import make_group_key


def _options(*, group_by="license", split_on_license=True, split_debian=True):
    flags = ["--no-progress", "--root", "tests/testdata/one", "--group-by", group_by]
    if not split_on_license:
        flags.append("--no-split-on-license")
    if not split_debian:
        flags.append("--no-split-debian")
    return cmdoptions.process_options(flags)


class MakeGroupKeyTest(unittest.TestCase):
    """grouping.make_group_key fills the copyright slot only under copyright."""

    def test_copyright_slot_is_none_when_grouping_by_license(self):
        key = make_group_key("MIT", ("alice@x",), "path", _options(group_by="license"))
        self.assertEqual(key, ("MIT", None, "path"))

    def test_copyright_slot_is_kept_when_grouping_by_copyright(self):
        key = make_group_key(
            "MIT", ("alice@x",), "path", _options(group_by="copyright")
        )
        self.assertEqual(key, ("MIT", ("alice@x",), "path"))

    def test_license_and_path_pass_through_unchanged(self):
        key = make_group_key("GPL-2", (), None, _options(group_by="copyright"))
        self.assertEqual(key, ("GPL-2", (), None))


class SplittingPathTest(unittest.TestCase):
    """file_policy.splitting_path resolves the per-node split boundary."""

    def setUp(self):
        # root/
        #   src/lic/   (lic holds a license)
        #   debian/
        self.root = nodes.DirInfo(parent=None, name="")
        self.src = nodes.DirInfo(parent=self.root, name="src")
        self.root.file_list["src"] = self.src
        self.debian = nodes.DirInfo(parent=self.root, name="debian")
        self.root.file_list["debian"] = self.debian
        self.lic = nodes.DirInfo(parent=self.src, name="lic")
        self.lic.add_licenses(["MIT"])
        self.src.file_list["lic"] = self.lic

    def test_root_returns_its_own_name_regardless_of_flags(self):
        self.assertEqual(file_policy.splitting_path(self.root, _options()), "")

    def test_license_dir_returns_itself_with_split_on_license(self):
        opts = _options(split_on_license=True)
        self.assertEqual(file_policy.splitting_path(self.lic, opts), "src/lic")

    def test_license_dir_recurses_to_root_without_split_on_license(self):
        opts = _options(split_on_license=False)
        self.assertEqual(file_policy.splitting_path(self.lic, opts), "")

    def test_debian_dir_is_special_with_split_debian(self):
        opts = _options(split_debian=True)
        self.assertEqual(file_policy.splitting_path(self.debian, opts), "debian")

    def test_debian_dir_recurses_to_root_without_split_debian(self):
        opts = _options(split_debian=False)
        self.assertEqual(file_policy.splitting_path(self.debian, opts), "")

    def test_file_delegates_to_its_parent_directory(self):
        opts = _options(split_on_license=True)
        f = nodes.FileInfo(parent=self.lic, name="a.txt")
        self.assertEqual(file_policy.splitting_path(f, opts), "src/lic")

    def test_parentless_file_has_no_splitting_path(self):
        orphan = nodes.FileInfo(parent=None, name="o.txt")
        self.assertIsNone(file_policy.splitting_path(orphan, _options()))

    # node subclasses follow their base kind (ArchiveDirInfo -> DirInfo,
    # VirtualFileInfo -> FileInfo); this is the distinction Item 5 makes
    # polymorphic, so pin it before the refactor.

    def test_archive_dir_is_split_boundary_like_a_directory(self):
        arch = nodes.ArchiveDirInfo(
            parent=self.src, name="pkg.tar", archive_path="/x/pkg.tar"
        )
        arch.add_licenses(["MIT"])
        self.assertEqual(
            file_policy.splitting_path(arch, _options(split_on_license=True)),
            "src/pkg.tar",
        )

    def test_archive_dir_recurses_when_not_a_boundary(self):
        arch = nodes.ArchiveDirInfo(
            parent=self.src, name="pkg.tar", archive_path="/x/pkg.tar"
        )
        arch.add_licenses(["MIT"])
        self.assertEqual(
            file_policy.splitting_path(arch, _options(split_on_license=False)),
            "",
        )

    def test_virtual_file_delegates_to_parent_like_a_file(self):
        vf = nodes.VirtualFileInfo(parent=self.lic, name="a.py", content=b"x")
        self.assertEqual(
            file_policy.splitting_path(vf, _options(split_on_license=True)),
            "src/lic",
        )


class RemoveMisplacedFilesTest(unittest.TestCase):
    """remove_misplaced_files: the group_by copyright fallback vs license."""

    def _file(self, name, holder=None):
        f = nodes.FileInfo(parent=self.root, name=name)
        f.add_licenses(["MIT"])
        if holder is not None:
            f.add_copyrights([holder])
        return f

    def _copyright_with_group(self, files):
        copyright_ = dep5.Copyright()
        copyright_.groups = []
        group = dep5.Group(position=0)
        for f in files:
            group.add_file(f)
        copyright_.groups.append(group)
        return copyright_, group

    def setUp(self):
        self.root = nodes.DirInfo(parent=None, name="")
        self.alice = CopyrightHolder("Alice", "alice@x")
        self.bob = CopyrightHolder("Bob", "bob@x")

    def test_uncopyrighted_file_is_kept_in_copyrighted_group(self):
        # Same-holder files plus an uncopyrighted one: under copyright grouping
        # the uncopyrighted file is rescued by the no-copyright fallback.
        files = [
            self._file("a.txt", self.alice),
            self._file("b.txt", self.alice),
            self._file("c.txt", None),
        ]
        copyright_, group = self._copyright_with_group(files)
        copyright_.remove_misplaced_files(_options(group_by="copyright"))
        self.assertEqual(sorted(group.files.files), ["a.txt", "b.txt", "c.txt"])

    def test_same_files_all_kept_under_license_grouping(self):
        files = [
            self._file("a.txt", self.alice),
            self._file("b.txt", self.alice),
            self._file("c.txt", None),
        ]
        copyright_, group = self._copyright_with_group(files)
        copyright_.remove_misplaced_files(_options(group_by="license"))
        self.assertEqual(sorted(group.files.files), ["a.txt", "b.txt", "c.txt"])

    def test_mismatched_copyright_files_dropped_under_copyright_grouping(self):
        # Distinct holders: the merged group key matches neither file, and a
        # non-empty copyright is not rescued by the fallback, so both drop.
        files = [self._file("a.txt", self.alice), self._file("b.txt", self.bob)]
        copyright_, group = self._copyright_with_group(files)
        copyright_.remove_misplaced_files(_options(group_by="copyright"))
        self.assertEqual(sorted(group.files.files), [])

    def test_mismatched_copyright_files_kept_under_license_grouping(self):
        # Under license grouping copyright is irrelevant, so both survive.
        files = [self._file("a.txt", self.alice), self._file("b.txt", self.bob)]
        copyright_, group = self._copyright_with_group(files)
        copyright_.remove_misplaced_files(_options(group_by="license"))
        self.assertEqual(sorted(group.files.files), ["a.txt", "b.txt"])


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