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

"""Tests for Options dataclass extraction to core_types.py"""

import dataclasses
import typing as t
import unittest

try:
    import regex as rex
except ImportError:
    import re as rex  # type: ignore[no-redef]

try:
    from decopy.common.core_types import Options
except ImportError as e:
    msg = "Cannot import Options from core_types module"
    raise ImportError(msg) from e


class TestOptionsClass(unittest.TestCase):
    """Test Options dataclass functionality in core_types module"""

    def setUp(self):
        """Set up test fixtures"""

    def _create_options(self, mode="full", copyright_file="debian/copyright", **kwargs):
        """Helper method to create Options with default required fields"""
        default_kwargs: dict[str, t.Any] = {
            "debug": False,
            "text": False,
            "group_by": "license",
            "split_on_license": False,
            "split_debian": False,
            "glob": False,
            "progress": False,
            "jobs": 1,
            "output": "",
            "root": ".",
            "files": [],
            "exclude_fullname_re": rex.compile(""),
            "exclude_special_re": rex.compile(""),
            "exclude_file_re": rex.compile(""),
            "exclude_directory_re": rex.compile(""),
        }
        default_kwargs.update(kwargs)
        return Options(mode=mode, copyright_file=copyright_file, **default_kwargs)

    def test_options_creation_full_mode(self):
        """Test Options creation with full mode"""
        options = self._create_options()
        self.assertIsNotNone(options)
        self.assertEqual(options.mode, "full")
        self.assertEqual(options.copyright_file, "debian/copyright")
        self.assertEqual(options.debug, False)
        self.assertEqual(options.text, False)
        self.assertEqual(options.group_by, "license")
        self.assertEqual(options.split_on_license, False)
        self.assertEqual(options.split_debian, False)
        self.assertEqual(options.glob, False)
        self.assertEqual(options.progress, False)

    def test_options_creation_partial_mode(self):
        """Test Options creation with partial mode"""
        options = self._create_options(
            mode="partial",
            copyright_file="debian/copyright",
            debug=True,
            text=True,
            group_by="copyright",
            split_on_license=True,
            split_debian=True,
            glob=True,
            progress=True,
        )
        self.assertIsNotNone(options)
        self.assertEqual(options.mode, "partial")
        self.assertEqual(options.copyright_file, "debian/copyright")
        self.assertEqual(options.debug, True)
        self.assertEqual(options.text, True)
        self.assertEqual(options.group_by, "copyright")
        self.assertEqual(options.split_on_license, True)
        self.assertEqual(options.split_debian, True)
        self.assertEqual(options.glob, True)
        self.assertEqual(options.progress, True)

    def test_options_dataclass_attributes(self):
        """Test Options dataclass has expected attributes"""
        options = self._create_options()

        # Test all expected attributes exist
        self.assertTrue(hasattr(options, "mode"))
        self.assertTrue(hasattr(options, "copyright_file"))
        self.assertTrue(hasattr(options, "debug"))
        self.assertTrue(hasattr(options, "text"))
        self.assertTrue(hasattr(options, "group_by"))
        self.assertTrue(hasattr(options, "split_on_license"))
        self.assertTrue(hasattr(options, "split_debian"))
        self.assertTrue(hasattr(options, "glob"))
        self.assertTrue(hasattr(options, "progress"))

    def test_options_mode_validation(self):
        """Test Options mode attribute validation"""
        # Test valid mode values
        full_options = Options(
            mode="full",
            copyright_file="debian/copyright",
            debug=False,
            text=False,
            group_by="license",
            split_on_license=False,
            split_debian=False,
            glob=False,
            progress=False,
            jobs=1,
            output="",
            root=".",
            files=[],
            exclude_fullname_re=rex.compile(""),
            exclude_special_re=rex.compile(""),
            exclude_file_re=rex.compile(""),
            exclude_directory_re=rex.compile(""),
        )
        self.assertEqual(full_options.mode, "full")

        partial_options = Options(
            mode="partial",
            copyright_file="debian/copyright",
            debug=False,
            text=False,
            group_by="license",
            split_on_license=False,
            split_debian=False,
            glob=False,
            progress=False,
            jobs=1,
            output="",
            root=".",
            files=[],
            exclude_fullname_re=rex.compile(""),
            exclude_special_re=rex.compile(""),
            exclude_file_re=rex.compile(""),
            exclude_directory_re=rex.compile(""),
        )
        self.assertEqual(partial_options.mode, "partial")

    def test_options_group_by_validation(self):
        """Test Options group_by attribute validation"""
        # Test valid group_by values
        license_group = self._create_options(group_by="license")
        self.assertEqual(license_group.group_by, "license")

        copyright_group = self._create_options(group_by="copyright")
        self.assertEqual(copyright_group.group_by, "copyright")

    def test_options_boolean_flags(self):
        """Test Options boolean flag attributes"""
        options_false = self._create_options(
            debug=False,
            text=False,
            split_on_license=False,
            split_debian=False,
            glob=False,
            progress=False,
        )

        options_true = self._create_options(
            debug=True,
            text=True,
            split_on_license=True,
            split_debian=True,
            glob=True,
            progress=True,
        )

        # Test all boolean flags
        boolean_flags = [
            "debug",
            "text",
            "split_on_license",
            "split_debian",
            "glob",
            "progress",
        ]

        for flag in boolean_flags:
            self.assertEqual(getattr(options_false, flag), False)
            self.assertEqual(getattr(options_true, flag), True)

    def test_options_string_attributes(self):
        """Test Options string attribute values"""
        options = self._create_options(
            copyright_file="custom/copyright",
        )

        # Test string attributes
        self.assertEqual(options.mode, "full")
        self.assertEqual(options.copyright_file, "custom/copyright")
        self.assertEqual(options.group_by, "license")

    def test_options_default_values(self):
        """Test Options with default values where applicable"""
        # Create options with minimal required parameters
        options = self._create_options()

        # Check that boolean flags default to False
        self.assertEqual(options.debug, False)
        self.assertEqual(options.text, False)
        self.assertEqual(options.split_on_license, False)
        self.assertEqual(options.split_debian, False)
        self.assertEqual(options.glob, False)
        self.assertEqual(options.progress, False)
        self.assertEqual(options.group_by, "license")  # Default from dataclass

    def test_options_immutability(self):
        """Options is frozen: fields cannot be reassigned after construction.

        The run's settings are resolved once (see decopy._resolve_run_options)
        and then read as an invariant by every downstream consumer.
        """
        options = self._create_options()

        # Attributes can be read ...
        self.assertEqual(options.mode, "full")

        # ... but not reassigned.
        with self.assertRaises(dataclasses.FrozenInstanceError):
            options.mode = "partial"

    def test_options_string_representation(self):
        """Test Options string representation"""
        options = self._create_options()

        str_repr = str(options)
        self.assertIsInstance(str_repr, str)
        self.assertIn("Options", str_repr)
        self.assertIn("mode='full'", str_repr)
        self.assertIn("copyright_file='debian/copyright'", str_repr)

    def test_options_representation(self):
        """Test Options representation"""
        options = self._create_options()

        repr_str = repr(options)
        self.assertIsInstance(repr_str, str)
        self.assertIn("Options", repr_str)

    def test_options_comparison(self):
        """Test Options equality comparison"""
        options1 = self._create_options()

        options2 = self._create_options()

        options3 = self._create_options(mode="partial")

        # Test equality
        self.assertEqual(options1, options2)
        self.assertNotEqual(options1, options3)

    def test_options_with_different_copyright_files(self):
        """Test Options with different copyright file paths"""
        test_cases = [
            "debian/copyright",
            "debian/copyright.d/copyright",
            "/usr/share/common-licenses/GPL",
            "custom/path/to/copyright",
            "COPYRIGHT",
        ]

        for copyright_file in test_cases:
            options = self._create_options(copyright_file=copyright_file)
            self.assertEqual(options.copyright_file, copyright_file)

    def test_options_complex_configuration(self):
        """Test Options with complex configuration"""
        options = self._create_options(
            mode="partial",
            copyright_file="debian/copyright.d/copyright",
            debug=True,
            text=True,
            group_by="copyright",
            split_on_license=True,
            split_debian=True,
            glob=True,
            progress=True,
        )

        # Verify all complex settings
        self.assertEqual(options.mode, "partial")
        self.assertEqual(options.copyright_file, "debian/copyright.d/copyright")
        self.assertEqual(options.debug, True)
        self.assertEqual(options.text, True)
        self.assertEqual(options.group_by, "copyright")
        self.assertEqual(options.split_on_license, True)
        self.assertEqual(options.split_debian, True)
        self.assertEqual(options.glob, True)
        self.assertEqual(options.progress, True)

    def test_options_minimal_required_parameters(self):
        """Test Options creation with only required parameters"""
        # mode and copyright_file are required, others have defaults
        options = self._create_options()

        # Verify default values for optional parameters
        self.assertEqual(options.debug, False)
        self.assertEqual(options.text, False)
        self.assertEqual(options.group_by, "license")
        self.assertEqual(options.split_on_license, False)
        self.assertEqual(options.split_debian, False)
        self.assertEqual(options.glob, False)
        self.assertEqual(options.progress, False)

    def test_options_ignore_defaults(self):
        """use_gitignore defaults to True and files_excluded to an empty tuple"""
        options = self._create_options()
        self.assertEqual(options.use_gitignore, True)
        self.assertEqual(options.files_excluded, ())


class TestIgnoreCmdOptions(unittest.TestCase):
    """The --gitignore/--no-gitignore CLI flag and files_excluded default."""

    def test_gitignore_default_on(self):
        from decopy import cmdoptions

        options = cmdoptions.process_options(["--no-progress"])
        self.assertEqual(options.use_gitignore, True)
        self.assertEqual(options.files_excluded, ())

    def test_no_gitignore_disables(self):
        from decopy import cmdoptions

        options = cmdoptions.process_options(["--no-progress", "--no-gitignore"])
        self.assertEqual(options.use_gitignore, False)


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