#!/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.

"""Unit tests for decopy._resolve_run_options.

The resolver settles the run's scope (Files-Excluded + git-changed set) into a
NEW, immutable Options and a deleted-paths tuple, without mutating its input.
These tests mock the git call, so they need no repository.
"""

from __future__ import annotations

import unittest
from pathlib import Path
from unittest.mock import patch

from decopy import decopy
from decopy.cmdoptions import process_options
from decopy.fstree import vcs

_ROOT = str(Path(__file__).parent / "testdata" / "one")


class ResolveRunOptionsTest(unittest.TestCase):
    def _options(self, *extra):
        return process_options(["--no-progress", "--root", _ROOT, *extra])

    def test_non_changed_sets_files_excluded_and_leaves_input(self):
        options = self._options()
        with patch.object(decopy, "read_files_excluded", return_value=("foo/*",)):
            resolved, deleted = decopy._resolve_run_options(options)
        self.assertIsNot(resolved, options)
        self.assertEqual(resolved.files_excluded, ("foo/*",))
        self.assertEqual(resolved.mode, "full")
        self.assertEqual(deleted, ())
        # The input is untouched.
        self.assertEqual(options.files_excluded, ())

    def test_changed_fallback_degrades_to_full_without_mutating(self):
        options = self._options("--changed")
        fallback = vcs.Fallback(reason="no-git", message="not a git repo")
        with (
            patch.object(decopy, "read_files_excluded", return_value=()),
            patch.object(decopy.vcs, "changed_paths", return_value=fallback),
        ):
            resolved, deleted = decopy._resolve_run_options(options)
        self.assertEqual(resolved.mode, "full")
        self.assertEqual(deleted, ())
        # The input mode is not rewritten.
        self.assertEqual(options.mode, "changed")

    def test_changed_unions_files_and_reports_deleted_without_mutating(self):
        options = self._options("--changed")
        original_files = list(options.files)
        scope = vcs.ChangedPaths(
            added_or_modified=("a.py", "b.py"),
            deleted=("old.py",),
            renamed=(("was.py", "now.py"),),
        )
        with (
            patch.object(decopy, "read_files_excluded", return_value=()),
            patch.object(decopy.vcs, "changed_paths", return_value=scope),
        ):
            resolved, deleted = decopy._resolve_run_options(options)
        self.assertEqual(
            set(resolved.files), {*original_files, "a.py", "b.py", "now.py"}
        )
        # deleted = removals plus the old side of renames, sorted.
        self.assertEqual(deleted, ("old.py", "was.py"))
        self.assertEqual(resolved.mode, "changed")
        # The input file list is untouched.
        self.assertEqual(options.files, original_files)


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