# -*- coding:utf-8 -*-
"""
Unit tests for ddns.scheduler.systemd module
@author: NewFuture
"""

import os
import platform
from __init__ import unittest, patch
from ddns.scheduler.systemd import SystemdScheduler


class TestSystemdScheduler(unittest.TestCase):
    """Test cases for SystemdScheduler class"""

    def setUp(self):
        """Set up test fixtures"""
        self.scheduler = SystemdScheduler()

    def test_service_name_property(self):
        """Test service name constant"""
        self.assertEqual(self.scheduler.SERVICE_NAME, "ddns.service")

    def test_timer_name_property(self):
        """Test timer name constant"""
        self.assertEqual(self.scheduler.TIMER_NAME, "ddns.timer")

    @patch("os.path.exists")
    def test_is_installed_true(self, mock_exists):
        """Test is_installed returns True when service exists"""
        mock_exists.return_value = True
        result = self.scheduler.is_installed()
        self.assertTrue(result)

    @patch("os.path.exists")
    def test_is_installed_false(self, mock_exists):
        """Test is_installed returns False when service doesn't exist"""
        mock_exists.return_value = False
        result = self.scheduler.is_installed()
        self.assertFalse(result)

    @patch("subprocess.check_output")
    @patch("ddns.scheduler.systemd.read_file_safely")
    @patch("os.path.exists")
    def test_get_status_success(self, mock_exists, mock_read_file, mock_check_output):
        """Test get_status with proper file reading"""
        mock_exists.return_value = True
        # Mock read_file_safely to return content for timer file and service file

        def mock_read_side_effect(file_path):
            if "ddns.timer" in file_path:
                return "OnUnitActiveSec=5m\n"
            elif "ddns.service" in file_path:
                return "ExecStart=/usr/bin/python3 -m ddns\n"
            return ""

        mock_read_file.side_effect = mock_read_side_effect
        # Mock subprocess.check_output to return "enabled" status
        mock_check_output.return_value = "enabled"

        status = self.scheduler.get_status()

        self.assertEqual(status["scheduler"], "systemd")
        self.assertTrue(status["installed"])
        self.assertTrue(status["enabled"])
        self.assertEqual(status["interval"], 5)

    @patch("ddns.scheduler.systemd.write_file")
    @patch.object(SystemdScheduler, "_systemctl")
    def test_install_with_sudo_fallback(self, mock_systemctl, mock_write_file):
        """Test install with sudo fallback for permission issues"""
        # Mock successful file writing and systemctl calls
        mock_write_file.return_value = None  # write_file doesn't return anything
        mock_systemctl.side_effect = [True, True, True]  # daemon-reload, enable, start all succeed

        ddns_args = {"dns": "debug", "ipv4": ["test.com"]}
        result = self.scheduler.install(5, ddns_args)
        self.assertTrue(result)

        # Verify that write_file was called twice (service and timer files)
        self.assertEqual(mock_write_file.call_count, 2)
        # Verify systemctl was called 3 times (daemon-reload, enable, start)
        self.assertEqual(mock_systemctl.call_count, 3)

    def test_systemctl_with_sudo_retry(self):
        """Test systemctl command with automatic sudo retry on permission error"""
        # Test that systemctl automatically retries with sudo when permission fails
        with patch.object(self.scheduler, "_run_command") as mock_run_cmd:
            mock_run_cmd.side_effect = [None, "success"]  # First fails, sudo succeeds
            self.scheduler._systemctl("enable", "ddns.timer")
            # Should still return success after sudo retry
            mock_run_cmd.assert_called()

    @patch("os.remove")
    @patch.object(SystemdScheduler, "_systemctl")
    def test_uninstall_with_permission_handling(self, mock_systemctl, mock_remove):
        """Test uninstall with proper permission handling"""
        mock_systemctl.return_value = True  # disable() succeeds

        # Mock successful file removal
        mock_remove.return_value = None

        result = self.scheduler.uninstall()
        self.assertTrue(result)

        # Verify both service and timer files are removed
        self.assertEqual(mock_remove.call_count, 2)

    def test_build_ddns_command(self):
        """Test _build_ddns_command functionality"""
        ddns_args = {"dns": "debug", "ipv4": ["test.example.com"], "debug": True}
        command = self.scheduler._build_ddns_command(ddns_args)
        self.assertIsInstance(command, str)
        self.assertIn("debug", command)
        self.assertIn("test.example.com", command)

    @unittest.skipUnless(platform.system().lower() == "linux", "Linux-specific test")
    def test_real_systemctl_availability(self):
        """Test if systemctl is available on Linux systems"""
        # Check if systemctl command is available
        try:
            systemctl_result = self.scheduler._run_command(["systemctl", "--version"])
            if not systemctl_result:
                self.skipTest("systemctl not available on this system")
        except Exception:
            self.skipTest("systemctl not available on this system")

        # Test both regular and sudo access
        self.scheduler._systemctl("--version")

        # Test if we have sudo access (don't actually run sudo commands in tests)
        try:
            sudo_result = self.scheduler._run_command(["sudo", "--version"])
            if sudo_result:
                # Just verify sudo is available for fallback
                self.assertIsNotNone(sudo_result)
        except Exception:
            # sudo not available, skip test
            self.skipTest("sudo not available for elevated permissions")

    @unittest.skipUnless(platform.system().lower() == "linux", "Linux-specific test")
    def test_permission_check_methods(self):
        """Test permission checking for systemd operations"""
        # Test if we can write to systemd directory
        systemd_dir = "/etc/systemd/system"
        can_write = os.access(systemd_dir, os.W_OK) if os.path.exists(systemd_dir) else False

        # If we can't write directly, we should be able to use sudo
        if not can_write:
            try:
                sudo_result = self.scheduler._run_command(["sudo", "--version"])
                self.assertIsNotNone(sudo_result, "sudo should be available for elevated permissions")
            except Exception:
                self.skipTest("sudo not available for elevated permissions")

    @unittest.skipUnless(platform.system().lower() == "linux", "Linux-specific test")
    def test_real_systemd_integration(self):
        """Test real systemd integration with actual system calls"""
        # Check if systemctl command is available
        try:
            systemctl_result = self.scheduler._run_command(["systemctl", "--version"])
            if not systemctl_result:
                self.skipTest("systemctl not available on this system")
        except Exception:
            self.skipTest("systemctl not available on this system")

        # Test real systemctl version call
        version_result = self.scheduler._systemctl("--version")
        # On a real Linux system with systemd, this should work
        # We don't assert the result since it may vary based on permissions
        self.assertIsInstance(version_result, bool)

        # Test real status check for a non-existent service
        status = self.scheduler.get_status()
        self.assertIsInstance(status, dict)
        self.assertEqual(status["scheduler"], "systemd")
        self.assertIsInstance(status["installed"], bool)

        # Test if daemon-reload works (read-only operation)
        daemon_reload_result = self.scheduler._systemctl("daemon-reload")
        # This might fail due to permissions, but shouldn't crash
        self.assertIsInstance(daemon_reload_result, bool)

    @unittest.skipUnless(platform.system().lower() == "linux", "Linux-specific test")
    def test_real_scheduler_methods_safe(self):
        """Test real scheduler methods that don't modify system state"""
        # Check if systemctl command is available
        try:
            systemctl_result = self.scheduler._run_command(["systemctl", "--version"])
            if not systemctl_result:
                self.skipTest("systemctl not available on this system")
        except Exception:
            self.skipTest("systemctl not available on this system")

        # Test is_installed (safe read-only operation)
        installed = self.scheduler.is_installed()
        self.assertIsInstance(installed, bool)

        # Test build command
        ddns_args = {"dns": "debug", "ipv4": ["test.example.com"]}
        command = self.scheduler._build_ddns_command(ddns_args)
        self.assertIsInstance(command, str)
        self.assertIn("python", command.lower())

        # Test get status (safe read-only operation)
        status = self.scheduler.get_status()
        # Basic keys should always be present
        basic_required_keys = ["scheduler", "installed"]
        for key in basic_required_keys:
            self.assertIn(key, status)

        # If service is installed, additional keys should be present
        if status.get("installed", False):
            additional_keys = ["enabled", "interval"]
            for key in additional_keys:
                self.assertIn(key, status)

        # Test enable/disable without actual installation (should handle gracefully)
        enable_result = self.scheduler.enable()
        self.assertIsInstance(enable_result, bool)

        disable_result = self.scheduler.disable()
        self.assertIsInstance(disable_result, bool)

    @unittest.skipUnless(platform.system().lower() == "linux", "Linux-specific test")
    def test_real_systemd_lifecycle_operations(self):
        """Test real systemd lifecycle operations: install -> enable -> disable -> uninstall"""
        # Check if systemctl command is available
        try:
            systemctl_result = self.scheduler._run_command(["systemctl", "--version"])
            if not systemctl_result:
                self.skipTest("systemctl not available on this system")
        except Exception:
            self.skipTest("systemctl not available on this system")

        # Test arguments for DDNS
        ddns_args = {"dns": "debug", "ipv4": ["test.example.com"], "interval": 10}

        # Store original state
        original_installed = self.scheduler.is_installed()
        self.scheduler.get_status() if original_installed else None

        try:
            # Test 1: Install operation
            install_result = self.scheduler.install(10, ddns_args)
            self.assertIsInstance(install_result, bool)

            # After install, service should be installed (regardless of permissions)
            post_install_status = self.scheduler.get_status()
            self.assertIsInstance(post_install_status, dict)
            self.assertEqual(post_install_status["scheduler"], "systemd")
            self.assertIsInstance(post_install_status["installed"], bool)

            # If installation succeeded, test enable/disable
            if install_result and post_install_status.get("installed", False):
                # Test 2: Enable operation
                enable_result = self.scheduler.enable()
                self.assertIsInstance(enable_result, bool)

                # Check status after enable attempt
                post_enable_status = self.scheduler.get_status()
                self.assertIsInstance(post_enable_status, dict)
                self.assertIn("enabled", post_enable_status)

                # Test 3: Disable operation
                disable_result = self.scheduler.disable()
                self.assertIsInstance(disable_result, bool)

                # Check status after disable attempt
                post_disable_status = self.scheduler.get_status()
                self.assertIsInstance(post_disable_status, dict)
                self.assertIn("enabled", post_disable_status)

                # Test 4: Uninstall operation
                uninstall_result = self.scheduler.uninstall()
                self.assertIsInstance(uninstall_result, bool)

                # Check status after uninstall attempt
                post_uninstall_status = self.scheduler.get_status()
                self.assertIsInstance(post_uninstall_status, dict)
                # After uninstall, installed should be False (if uninstall succeeded)
                if uninstall_result:
                    self.assertFalse(post_uninstall_status.get("installed", True))
            else:
                self.skipTest("Install failed due to permissions - cannot test lifecycle")

        except Exception as e:
            # If we get permission errors, that's expected in test environment
            if "Permission denied" in str(e) or "Interactive authentication required" in str(e):
                self.skipTest("Insufficient permissions for systemd operations")
            else:
                # Re-raise unexpected exceptions
                raise

        finally:
            # Cleanup: Try to restore original state
            try:
                if original_installed:
                    # If it was originally installed, try to restore
                    if not self.scheduler.is_installed():
                        # Try to reinstall with original settings if we have them
                        self.scheduler.install(10, ddns_args)
                else:
                    # If it wasn't originally installed, try to uninstall
                    if self.scheduler.is_installed():
                        self.scheduler.uninstall()
            except Exception:
                # Cleanup failures are not critical for tests
                pass

    @unittest.skipUnless(platform.system().lower() == "linux", "Linux-specific test")
    def test_real_systemd_status_consistency(self):
        """Test that systemd status reporting is consistent across operations"""
        # Check if systemctl command is available
        try:
            systemctl_result = self.scheduler._run_command(["systemctl", "--version"])
            if not systemctl_result:
                self.skipTest("systemctl not available on this system")
        except Exception:
            self.skipTest("systemctl not available on this system")

        # Get initial status
        initial_status = self.scheduler.get_status()
        self.assertIsInstance(initial_status, dict)
        self.assertEqual(initial_status["scheduler"], "systemd")
        self.assertIn("installed", initial_status)

        # Test is_installed consistency
        installed_check = self.scheduler.is_installed()
        self.assertEqual(installed_check, initial_status["installed"])

        # If installed, check that additional status fields are present
        if initial_status.get("installed", False):
            required_keys = ["enabled", "interval"]
            for key in required_keys:
                self.assertIn(key, initial_status, "Key '{}' should be present when service is installed".format(key))

        # Test that repeated status calls are consistent
        second_status = self.scheduler.get_status()
        self.assertEqual(initial_status["scheduler"], second_status["scheduler"])
        self.assertEqual(initial_status["installed"], second_status["installed"])

        # If both report as installed, other fields should also match
        if initial_status.get("installed", False) and second_status.get("installed", False):
            for key in ["enabled", "interval"]:
                if key in initial_status and key in second_status:
                    self.assertEqual(
                        initial_status[key],
                        second_status[key],
                        "Status field '{}' should be consistent between calls".format(key),
                    )


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