import unittest
from unittest.mock import patch, MagicMock
import os
import sys

# Add project root
sys.path.append(os.getcwd())

# Mock psutil BEFORE importing modules that use it
mock_psutil = MagicMock()
sys.modules["psutil"] = mock_psutil

from slack_bot.tools.file_ops import download_file, save_file

# We need to reload or import these after mocking psutil if they were already imported,
# but since this is a clean run, it should be fine.
# Note: system_ops and process_ops import psutil at top level.
from slack_bot.tools.system_ops import get_system_status
from slack_bot.tools.process_ops import kill_process, get_process_list

class TestShellTools(unittest.TestCase):

    # --- File Ops Tests ---
    @patch("slack_bot.tools.file_ops.WebClient")
    @patch("os.path.exists")
    @patch("os.path.getsize")
    def test_download_file(self, mock_getsize, mock_exists, mock_client):
        # Setup
        mock_exists.return_value = True
        mock_getsize.return_value = 1024 # 1KB
        
        # Test Success
        res = download_file("/tmp/test.log", "C123")
        self.assertIn("Successfully uploaded", res)
        mock_client.return_value.files_upload_v2.assert_called_once()
        
        # Test File Too Large
        mock_getsize.return_value = 60 * 1024 * 1024 # 60MB
        res = download_file("/tmp/huge.log", "C123")
        self.assertIn("File too large", res)

    def test_save_file(self):
        test_path = "/tmp/butler_test_file.txt"
        content = "Hello ShellBot"
        
        # Test Write
        res = save_file(test_path, content, overwrite=True)
        self.assertIn("Successfully saved", res)
        
        with open(test_path, "r") as f:
            self.assertEqual(f.read(), content)
            
        # Cleanup
        os.remove(test_path)

    # --- System Ops Tests ---
    def test_get_system_status(self):
        # Configure mock_psutil
        mock_psutil.boot_time.return_value = 1600000000.0
        mock_psutil.cpu_percent.return_value = 15.5
        mock_psutil.cpu_count.return_value = 4
        mock_psutil.getloadavg.return_value = (1.0, 0.8, 0.5)
        
        mock_mem = MagicMock()
        mock_mem.percent = 45.0
        mock_mem.used = 8 * 1024**3
        mock_mem.total = 16 * 1024**3
        mock_psutil.virtual_memory.return_value = mock_mem
        
        mock_swap = MagicMock()
        mock_swap.percent = 10.0
        mock_swap.used = 1 * 1024**3
        mock_psutil.swap_memory.return_value = mock_swap
        
        mock_disk = MagicMock()
        mock_disk.percent = 60.0
        mock_disk.used = 120 * 1024**3
        mock_disk.total = 256 * 1024**3
        mock_psutil.disk_usage.return_value = mock_disk
        
        mock_proc = MagicMock()
        mock_proc.info = {
            'pid': 999, 'name': 'resource_hog', 
            'username': 'root', 
            'cpu_percent': 80.0, 'memory_percent': 20.0,
            'status': 'running'
        }
        mock_psutil.process_iter.return_value = [mock_proc]

        res = get_system_status()
        print(f"\n[System Status Output]\n{res}\n")
        self.assertIn("System Status Application", res)
        self.assertIn("CPU", res)
        self.assertIn("Memory", res)
        self.assertIn("resource_hog", res)

    # --- Process Ops Tests ---
    def test_get_process_list(self):
        # Configure mock for this test
        p1 = MagicMock()
        p1.info = {'pid': 1, 'name': 'init', 'username': 'root', 'cpu_percent': 0.1, 'memory_percent': 0.5, 'status': 'sleeping'}
        p2 = MagicMock()
        p2.info = {'pid': 2, 'name': 'python', 'username': 'user', 'cpu_percent': 50.0, 'memory_percent': 2.0, 'status': 'running'}
        mock_psutil.process_iter.return_value = [p1, p2]

        # Real run
        res = get_process_list(limit=5)
        print(f"\n[Process List Output]\n{res}\n")
        self.assertIn("Process List", res)
        self.assertIn("python", res) # Should be listed
        self.assertIn("init", res)
        
        # Test Filter
        res_filter = get_process_list(filter_name="python")
        self.assertIn("python", res_filter)
        self.assertNotIn("init", res_filter)

    @patch("psutil.Process")
    @patch("psutil.pid_exists")
    def test_kill_process(self, mock_exists, mock_process):
        mock_exists.return_value = True
        mock_proc_instance = MagicMock()
        mock_proc_instance.name.return_value = "check_process"
        mock_process.return_value = mock_proc_instance
        
        res = kill_process(12345, "SIGTERM")
        self.assertIn("Sent SIGTERM", res)
        mock_proc_instance.terminate.assert_called_once()

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