from odoo import exceptions
from odoo.exceptions import AccessError

from .common import TestTowerCommon


class TestTowerFile(TestTowerCommon):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        cls.file_template = cls.FileTemplate.create(
            {
                "name": "Test",
                "file_name": "test.txt",
                "server_dir": "/var/tmp",
                "code": "Hello, world!",
            }
        )
        cls.file = cls.File.create(
            {
                "name": "tower_demo_1.txt",
                "source": "tower",
                "template_id": cls.file_template.id,
                "server_id": cls.server_test_1.id,
            }
        )
        cls.file_2 = cls.File.create(
            {
                "name": "test.txt",
                "source": "server",
                "server_id": cls.server_test_1.id,
                "server_dir": "/var/tmp",
            }
        )

        # Create a dummy Server record that will be referenced by file records.
        cls.server = cls.Server.create(
            {
                "name": "Test Server",
                "manager_ids": [(6, 0, [cls.manager.id])],
                "user_ids": [(6, 0, [cls.user.id])],
                "ssh_username": "admin",
                "ssh_password": "password",
                "ssh_auth_mode": "p",
                "skip_host_key": True,
                "os_id": cls.os_debian_10.id,
                "ip_v4_address": "localhost",
            }
        )

    def test_user_read_access(self):
        """
        Test that a user in the custom User group can read a file record
        when their ID is in the related server's user_ids.
        """
        file_record = self.File.create(
            {
                "name": "Test File",
                "server_dir": "/tmp",
                "file_type": "text",
                "source": "tower",
                "server_id": self.server.id,
            }
        )
        # As the user, the file record should be visible.
        files_for_user = self.File.with_user(self.user).search(
            [("id", "=", file_record.id)]
        )
        self.assertTrue(
            files_for_user,
            "User should be able to read the file record "
            "because they are in server.user_ids.",
        )

        # Remove user from server.user_ids.
        self.server.write({"user_ids": [(3, self.user.id)]})
        files_for_user = self.File.with_user(self.user).search(
            [("id", "=", file_record.id)]
        )
        self.assertFalse(
            files_for_user,
            "User should not be able to read the file record "
            "because he is not in server.user_ids.",
        )

    def test_manager_write_create_access(self):
        """
        Test that a manager in the custom Manager group can create and write
        file records when his ID is in the related server's manager_ids.
        """
        # Test creation: the manager is in server.manager_ids.
        file_record = self.File.with_user(self.manager).create(
            {
                "name": "Manager Created File",
                "server_dir": "/tmp",
                "file_type": "text",
                "source": "tower",
                "server_id": self.server.id,
            }
        )
        self.assertTrue(
            file_record,
            "Manager should be able to create a file record "
            "because they are in server.manager_ids.",
        )

        # Test updating (write access).
        try:
            file_record.with_user(self.manager).write({"name": "Manager Updated File"})
        except AccessError:
            self.fail(
                "Manager should be able to update the file record "
                "because he is in server.manager_ids."
            )
        self.assertEqual(
            file_record.with_user(self.manager).name,
            "Manager Updated File",
            "File record name should be updated by the manager.",
        )

        # Test that a manager who is not in the server's manager_ids
        # cannot write or create.
        # Remove manager from server.manager_ids.
        self.server.write({"manager_ids": [(3, self.manager.id)]})
        # Create a file record on this server.
        file_record2 = self.File.create(
            {
                "name": "File on Server Without Manager",
                "server_dir": "/tmp",
                "file_type": "text",
                "source": "tower",
                "server_id": self.server.id,
            }
        )
        with self.assertRaises(AccessError):
            file_record2.with_user(self.manager).write({"name": "Should Not Update"})

        # Test create access for a manager not in manager_ids.
        with self.assertRaises(AccessError):
            self.File.with_user(self.manager).create(
                {
                    "name": "Invalid File",
                    "server_dir": "/tmp",
                    "file_type": "text",
                    "source": "tower",
                    "server_id": self.server.id,
                }
            )

    def test_manager_unlink_access(self):
        """
        Test that a manager in the custom Manager group can unlink (delete) a file
        record only if he is in the related server's manager_ids
        and they are the record's creator.
        """
        # Scenario 1: Record created by the manager.
        file_record = self.File.with_user(self.manager).create(
            {
                "name": "File to Delete",
                "server_dir": "/tmp",
                "file_type": "text",
                "source": "tower",
                "server_id": self.server.id,
            }
        )
        try:
            file_record.with_user(self.manager).unlink()
        except AccessError:
            self.fail(
                "Manager should be able to delete their own file"
                " record when in server.manager_ids."
            )

        # Scenario 2: Record created by someone else (e.g., the admin).
        file_record2 = self.File.create(
            {
                "name": "File Not Deletable by Manager",
                "server_dir": "/tmp",
                "file_type": "text",
                "source": "tower",
                "server_id": self.server.id,
            }
        )
        with self.assertRaises(AccessError):
            file_record2.with_user(self.manager).unlink()

    def test_upload_file(self):
        """
        Upload file from tower to server
        """
        self.file.action_push_to_server()
        self.assertEqual(self.file.server_response, "ok")

    def test_delete_file(self):
        """
        Delete file remotely from server
        """
        result = self.file.action_delete_from_server()
        self.assertTrue(isinstance(result, dict))
        self.assertEqual(result["params"]["message"], "File deleted!")

    def test_delete_file_access(self):
        """
        Test delete file access
        """
        with self.assertRaises(exceptions.AccessError):
            self.file.with_user(self.user_bob).delete(raise_error=True)

    def test_download_file(self):
        """
        Download file from server to tower
        """
        self.file_2.action_pull_from_server()
        self.assertEqual(self.file_2.code, "ok")

        self.file_2.name = "binary.zip"
        res = self.file_2.action_pull_from_server()
        self.assertTrue(
            isinstance(res, dict) and res["tag"] == "display_notification",
            msg=(
                "If file type is 'text', then the result must be a dict "
                "representing the display_notification action."
            ),
        )

    def test_get_current_server_code(self):
        """
        Download file from server to tower
        """
        self.file.action_push_to_server()
        self.assertEqual(self.file.server_response, "ok")

        self.file.action_get_current_server_code()
        self.assertEqual(self.file.code_on_server, "ok")

    def test_modify_template_code(self):
        """Test how template code modification affects related files"""
        code = "Pepe frog is happy as always"
        self.file_template.code = code

        # Check file code before modifications
        self.assertTrue(
            self.file.code == code,
            msg="File code must be the same "
            "as template code before any modifications",
        )
        # Check file rendered code before modifications
        self.assertTrue(
            self.file.rendered_code == code,
            msg="File rendered code must be the same"
            " as template code before any modifications",
        )

        # Make possible to modify file code
        self.file.action_unlink_from_template()

        # Check if template was removed from file
        self.assertFalse(
            self.file.template_id,
            msg="File template should be removed after modifying code.",
        )

        # Check if file code remains the same
        self.assertTrue(
            self.file.code == code, msg="File code should be the same as template."
        )

    def test_modify_template_related_files(self):
        """
        Check that after change file template
        all related files will update
        """
        self.assertEqual(self.file_template.file_name, "test.txt")
        # related files
        self.assertTrue(
            all(file.name == "test.txt" for file in self.file_template.file_ids)
        )

        # update file template name
        self.file_template.file_name = "new_test.txt"
        # Related files must updated
        self.assertTrue(
            all(file.name == "new_test.txt" for file in self.file_template.file_ids)
        )

        self.assertEqual(self.file_template.code, "Hello, world!")
        # update file template code
        self.file_template.code = "New code"
        # Related files must updated
        self.assertTrue(
            all(file.code == "New code" for file in self.file_template.file_ids)
        )

    def test_create_file_with_template(self):
        """
        Test if file is created with template code
        """
        file_template = self.env["cx.tower.file.template"].create(
            {
                "name": "Test",
                "file_name": "test.txt",
                "server_dir": "/var/tmp",
                "code": "Hello, world!",
            }
        )

        file = file_template.create_file(
            server=self.server_test_1,
            server_dir=file_template.server_dir,
            if_file_exists="overwrite",
        )
        self.assertEqual(file.code, self.file_template.code)
        self.assertEqual(file.template_id, file_template)
        self.assertEqual(file.server_id, self.server_test_1)
        self.assertEqual(file.source, "tower")
        self.assertEqual(file.server_dir, self.file_template.server_dir)

        with self.assertRaises(exceptions.ValidationError):
            file_template.create_file(
                server=self.server_test_1,
                server_dir=file_template.server_dir,
                if_file_exists="raise",
            )

        another_file = file_template.create_file(
            server=self.server_test_1,
            server_dir=file_template.server_dir,
            if_file_exists="skip",
        )
        self.assertEqual(another_file, file)

    def test_create_file_with_template_custom_server_dir(self):
        """
        Test if file is created with template code and custom server dir
        """
        file_template = self.env["cx.tower.file.template"].create(
            {
                "name": "Test",
                "file_name": "test.txt",
                "server_dir": "/var/tmp",
                "code": "Hello, world!",
            }
        )

        file = file_template.create_file(
            server=self.server_test_1, server_dir="/var/tmp/custom"
        )
        self.assertEqual(file.code, self.file_template.code)
        self.assertEqual(file.template_id, file_template)
        self.assertEqual(file.server_id, self.server_test_1)
        self.assertEqual(file.source, "tower")
        self.assertEqual(file.server_dir, "/var/tmp/custom")

        with self.assertRaises(exceptions.ValidationError):
            file_template.create_file(
                server=self.server_test_1,
                server_dir="/var/tmp/custom",
                if_file_exists="raise",
            )

        another_file = file_template.create_file(
            server=self.server_test_1,
            server_dir="/var/tmp/custom",
            if_file_exists="skip",
        )
        self.assertEqual(another_file, file)

    def test_file_with_secret_key(self):
        """
        Test case to verify that when a file includes a secret reference,
        the secret key is automatically linked with the file.
        """

        # Create a secret key
        secret_python_key = self.Key.create(
            {
                "name": "python",
                "reference": "PYTHON",
                "secret_value": "secretPythonCode",
                "key_type": "s",
            }
        )

        # Create a file template with a reference to the secret key
        file_template = self.env["cx.tower.file.template"].create(
            {
                "name": "Test",
                "file_name": "test.txt",
                "server_dir": "/var/tmp",
                "code": "Please use this secret #!cxtower.secret.PYTHON!#",
            }
        )

        # Create a file from the file template
        file = file_template.create_file(
            server=self.server_test_1, server_dir="/var/tmp/custom"
        )

        # Assert that the file's code matches the file template's code
        self.assertEqual(
            file.code,
            file_template.code,
            msg="The file's code does not match the file template's code.",
        )

        # Assert that the secret key is associated with the file
        self.assertIn(
            secret_python_key,
            file.secret_ids,
            msg="The secret key is not associated with the file.",
        )

        # Update the file's code to remove the secret reference
        file.code = "Only text"

        self.assertFalse(
            file.secret_ids,
            msg=(
                "The secret_ids field should be empty after "
                "removing the secret reference from file."
            ),
        )

    def test_file_with_sensitive_variable(self):
        """
        Test case to verify that user has access to use file with sensitive variables.
        """
        # Create file with sensitive variable
        file = self.File.create(
            {
                "source": "tower",
                "name": "test.txt",
                "server_id": self.server_test_1.id,
                "code": "'IPv4 Address': {{ tower.server.ipv4 }}",
            }
        )
        # Remove user_bob from all cx_tower_server groups
        self.remove_from_group(
            self.user_bob,
            [
                "cetmix_tower_server.group_user",
                "cetmix_tower_server.group_manager",
                "cetmix_tower_server.group_root",
            ],
        )
        # Add bob to user group
        self.add_to_group(self.user_bob, "cetmix_tower_server.group_user")
        # Add bob as subscriber of the server to allow upload file
        self.server_test_1.write({"user_ids": [(4, self.user_bob.id)]})
        # Upload file to server
        self.assertTrue(file.server_response != "ok")
        file.with_user(self.user_bob).action_push_to_server()
        self.assertEqual(file.server_response, "ok")

    def test_sanitize_values(self):
        """
        Test case to verify that the sanitize_values method works correctly.
        """
        # 1. Root directory
        values = self.File._sanitize_values({"server_dir": "/"})
        self.assertEqual(values["server_dir"], "/")

        # 2. Trailing slash
        values = self.File._sanitize_values({"server_dir": "/var/tmp/"})
        self.assertEqual(values["server_dir"], "/var/tmp")

        # 3. Trailing whitespace
        values = self.File._sanitize_values({"server_dir": "/var/tmp/ "})
        self.assertEqual(values["server_dir"], "/var/tmp")

        # 4. Leading whitespace
        values = self.File._sanitize_values({"server_dir": " /var/tmp/"})
        self.assertEqual(values["server_dir"], "/var/tmp")

        # 5. Leading and trailing whitespace
        values = self.File._sanitize_values({"server_dir": " /var/tmp/ "})
        self.assertEqual(values["server_dir"], "/var/tmp")

        # 6. Leading and trailing whitespace just one slash
        values = self.File._sanitize_values({"server_dir": " / "})
        self.assertEqual(values["server_dir"], "/")
