# SPDX-FileCopyrightText: 2019-2024 Greenbone AG
#
# SPDX-License-Identifier: GPL-3.0-or-later
#

import os
import tempfile
import unittest
from pathlib import Path
from tempfile import TemporaryDirectory
from unittest.mock import Mock

from autohooks.hooks import PreCommitHook, get_pre_commit_hook_path
from autohooks.settings import Mode
from autohooks.template import (
    PIPENV_MULTILINE_SHEBANG,
    PIPENV_SHEBANG,
    POETRY_MULTILINE_SHEBANG,
    POETRY_SHEBANG,
    PYTHON3_SHEBANG,
    TEMPLATE_VERSION,
    PreCommitTemplate,
)
from autohooks.utils import exec_git


class GitDirTestCase(unittest.TestCase):
    def setUp(self):
        self.tempdir = TemporaryDirectory()
        self.temp_dir_path = Path(self.tempdir.name).resolve()

        exec_git("-C", str(self.temp_dir_path), "init")

        self.git_dir_path = self.temp_dir_path / ".git"

        self.assertTrue(self.git_dir_path.exists())

        os.chdir(str(self.temp_dir_path))

    def tearDown(self):
        self.tempdir.cleanup()


class GetPreCommitHookPathTestCase(GitDirTestCase):
    def test_get_path(self):
        pre_commmit_hook_path = get_pre_commit_hook_path()

        self.assertEqual(
            pre_commmit_hook_path,
            self.temp_dir_path / ".git" / "hooks" / "pre-commit",
        )


class InstallPreCommitHook(GitDirTestCase):
    def test_install(self):
        pre_commit_hook = PreCommitHook()

        self.assertFalse(pre_commit_hook.exists())

        pre_commit_hook.write(mode=Mode.PIPENV)

        self.assertTrue(pre_commit_hook.exists())


class FakeReadPath:
    def __init__(self, text):
        self._text = text

    def read_text(self):
        return self._text


class IsAutohooksPreCommitHook(unittest.TestCase):
    def test_other_hook(self):
        path = FakeReadPath("foo\nbar")
        pre_commit_hook = PreCommitHook(path)

        self.assertFalse(pre_commit_hook.is_autohooks_pre_commit_hook())

    def test_pre_commit_template(self):
        template = PreCommitTemplate()
        path = FakeReadPath(template.render(mode=Mode.PIPENV))
        pre_commit_hook = PreCommitHook(path)

        self.assertTrue(pre_commit_hook.is_autohooks_pre_commit_hook())


class IsCurrentAutohooksPreCommitHook(unittest.TestCase):
    def test_other_hook(self):
        path = FakeReadPath("foo\nbar")
        pre_commit_hook = PreCommitHook(path)

        self.assertFalse(pre_commit_hook.is_current_autohooks_pre_commit_hook())

    def test_pre_commit_template(self):
        template = PreCommitTemplate()
        path = FakeReadPath(template.render(mode=Mode.PIPENV))
        pre_commit_hook = PreCommitHook(path)

        self.assertTrue(pre_commit_hook.is_current_autohooks_pre_commit_hook())

    def test_modified_pre_commit_template(self):
        template = PreCommitTemplate()
        rendered = template.render(mode=Mode.PIPENV)
        lines = rendered.split("\n")
        lines[1] = ""
        path = FakeReadPath("\n".join(lines))
        pre_commit_hook = PreCommitHook(path)

        self.assertFalse(pre_commit_hook.is_current_autohooks_pre_commit_hook())


class ReadVersionTestCase(unittest.TestCase):
    def test_read_version(self):
        template = PreCommitTemplate()
        with tempfile.TemporaryDirectory() as tempdir:
            tmp_hook_path = Path(tempdir) / "pre-commit-test"
            # Find version using all shebang modes
            for mode in [m for m in Mode if m.value > 0]:
                with open(str(tmp_hook_path), "w", encoding="utf-8") as tmpfile:
                    tmpfile.write(template.render(mode=mode))
                pre_commit_hook = PreCommitHook(tmp_hook_path)

            self.assertEqual(TEMPLATE_VERSION, pre_commit_hook.read_version())

    def test_empty_content(self):
        path = FakeReadPath("")
        pre_commit_hook = PreCommitHook(path)

        self.assertEqual(pre_commit_hook.read_version(), -1)

    def test_no_meta(self):
        path = FakeReadPath("\n# foo = bar")
        pre_commit_hook = PreCommitHook(path)

        self.assertEqual(pre_commit_hook.read_version(), -1)


class ReadModeTestCase(unittest.TestCase):
    def test_undefined_mode(self):
        path = FakeReadPath("")
        pre_commit_hook = PreCommitHook(path)

        self.assertEqual(pre_commit_hook.read_mode(), Mode.UNDEFINED)

    def test_unknown_mode(self):
        path = FakeReadPath("#!foo")
        pre_commit_hook = PreCommitHook(path)

        self.assertEqual(pre_commit_hook.read_mode(), Mode.UNKNOWN)

    def test_pipenv_mode(self):
        path = FakeReadPath(f"#!{PIPENV_SHEBANG}")
        pre_commit_hook = PreCommitHook(path)

        self.assertEqual(pre_commit_hook.read_mode(), Mode.PIPENV)

    def test_poetry_mode(self):
        path = FakeReadPath(f"#!{POETRY_SHEBANG}")
        pre_commit_hook = PreCommitHook(path)

        self.assertEqual(pre_commit_hook.read_mode(), Mode.POETRY)

    def test_poetry_mode_with_python3(self):
        path = FakeReadPath(f"#!{POETRY_SHEBANG}3")
        pre_commit_hook = PreCommitHook(path)

        self.assertEqual(pre_commit_hook.read_mode(), Mode.POETRY)

    def test_pipenv_multiline_mode(self):
        path = FakeReadPath(f"#!{PIPENV_MULTILINE_SHEBANG}")
        pre_commit_hook = PreCommitHook(path)

        self.assertEqual(pre_commit_hook.read_mode(), Mode.PIPENV_MULTILINE)

    def test_poetry_multiline_mode(self):
        path = FakeReadPath(f"#!{POETRY_MULTILINE_SHEBANG}")
        pre_commit_hook = PreCommitHook(path)

        self.assertEqual(pre_commit_hook.read_mode(), Mode.POETRY_MULTILINE)

    def test_pythonpath_mode(self):
        path = FakeReadPath(f"#!{PYTHON3_SHEBANG}")
        pre_commit_hook = PreCommitHook(path)

        self.assertEqual(pre_commit_hook.read_mode(), Mode.PYTHONPATH)


class WriteTestCase(unittest.TestCase):
    def test_pipenv_mode(self):
        write_path = Mock()
        pre_commit_hook = PreCommitHook(write_path)
        pre_commit_hook.write(mode=Mode.PIPENV)

        write_path.chmod.assert_called_with(0o775)
        self.assertTrue(write_path.write_text.called)

        args, _kwargs = write_path.write_text.call_args
        text = args[0]
        self.assertRegex(text, f"^#!{PIPENV_SHEBANG} *")

    def test_poetry_mode(self):
        write_path = Mock()
        pre_commit_hook = PreCommitHook(write_path)
        pre_commit_hook.write(mode=Mode.POETRY)

        write_path.chmod.assert_called_with(0o775)
        self.assertTrue(write_path.write_text.called)

        args, _kwargs = write_path.write_text.call_args
        text = args[0]
        self.assertRegex(text, f"^#!{POETRY_SHEBANG} *")

    def test_pythonpath_mode(self):
        write_path = Mock()
        pre_commit_hook = PreCommitHook(write_path)
        pre_commit_hook.write(mode=Mode.PYTHONPATH)

        write_path.chmod.assert_called_with(0o775)
        self.assertTrue(write_path.write_text.called)

        args, _kwargs = write_path.write_text.call_args
        text = args[0]
        self.assertRegex(text, f"^#!{PYTHON3_SHEBANG} *")


class StrTestCase(unittest.TestCase):
    def test_str_conversion(self):
        path = Mock()
        path.__str__ = Mock(return_value="foo")

        pre_commit_hook = PreCommitHook(path)

        self.assertEqual(str(pre_commit_hook), "foo")
        path.__str__.assert_called_with()


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