#!/usr/bin/python3
# -*- coding: utf-8 -*-

import signal
import pytest
from unittest.mock import patch

from sun.manager import Manager


class TestDaemonStatus:

    def test_running(self):
        tools = Manager()
        with patch.object(tools.utils, 'get_daemon_pid', return_value='1234'):
            with patch('os.kill'):
                with patch('sun.manager.Path.is_file', return_value=True):
                    with patch('sun.manager.Path.read_text', return_value='State:\tS (sleeping)'):
                        assert tools.daemon_status() is True

    def test_not_running_no_pid(self):
        tools = Manager()
        with patch.object(tools.utils, 'get_daemon_pid', return_value=''):
            with patch.object(tools.utils, 'process_pids', return_value=[]):
                assert tools.daemon_status() is False

    def test_fallback_pid_recovery(self):
        tools = Manager()
        with patch.object(tools.utils, 'get_daemon_pid', return_value=''):
            with patch.object(tools.utils, 'process_pids', return_value=['5678']):
                with patch('pathlib.Path.mkdir'):
                    with patch('pathlib.Path.write_text') as mock_write:
                        result = tools.daemon_status()
                        assert result is True
                        mock_write.assert_called_once_with('5678')

    def test_stale_pid(self):
        tools = Manager()
        with patch.object(tools.utils, 'get_daemon_pid', return_value='9999'):
            with patch('os.kill', side_effect=ProcessLookupError):
                with patch('pathlib.Path.unlink'):
                    assert tools.daemon_status() is False

    def test_proc_status_missing_after_kill_reported_as_not_running(self):
        tools = Manager()
        with patch.object(tools.utils, 'get_daemon_pid', return_value='1234'):
            with patch('os.kill'):
                with patch('sun.manager.Path.is_file', return_value=False):
                    with patch('sun.manager.Path.unlink'):
                        assert tools.daemon_status() is False

    def test_zombie_process_reported_as_not_running(self):
        tools = Manager()
        zombie_status = 'Name:\tsun-daemon\nState:\tZ (zombie)\nPid:\t1234\n'
        with patch.object(tools.utils, 'get_daemon_pid', return_value='1234'):
            with patch('os.kill'):
                with patch('pathlib.Path.is_file', return_value=True):
                    with patch('pathlib.Path.read_text', return_value=zombie_status):
                        with patch('pathlib.Path.unlink'):
                            assert tools.daemon_status() is False


class TestCheckUpdates:

    def test_no_updates(self):
        tools = Manager()
        with patch.object(tools.fetch, 'updates', return_value=iter([])):
            message, packages = tools.check_updates()
            assert message == 'No news is good news!'
            assert packages == []

    def test_fetch_error_returns_error_message(self):
        tools = Manager()
        tools.fetch.last_fetch_error = True
        with patch.object(tools.fetch, 'updates', return_value=iter([])):
            message, packages = tools.check_updates()
            assert 'Error' in message
            assert packages == []

    def test_with_updates(self):
        tools = Manager()
        pkgs = ['vim-9.0-x86_64-1.txz', 'curl-7.85-x86_64-1.txz']
        with patch.object(tools.fetch, 'updates', return_value=iter(pkgs)):
            message, packages = tools.check_updates()
            assert '2 Software Updates Available' in message
            assert packages == pkgs


class TestDaemonStart:

    def test_start_launches_process(self):
        tools = Manager()
        with patch('subprocess.Popen') as mock_popen:
            mock_popen.return_value.wait = lambda: None
            tools._daemon_start()
            mock_popen.assert_called_once()


class TestDaemonStop:

    def test_stop_sends_sigterm(self):
        tools = Manager()
        with patch.object(tools.utils, 'get_daemon_pid', return_value='1234'):
            with patch('os.kill') as mock_kill:
                with patch('pathlib.Path.unlink'):
                    tools._daemon_stop()
                    mock_kill.assert_called_once_with(1234, signal.SIGTERM)

    def test_stop_raises_if_no_pid(self):
        tools = Manager()
        with patch.object(tools.utils, 'get_daemon_pid', return_value=''):
            with pytest.raises(OSError):
                tools._daemon_stop()


class TestDaemonProcess:

    def test_start_when_already_running(self):
        tools = Manager()
        with patch.object(tools, 'daemon_status', return_value=True):
            result = tools.daemon_process('start', 'Starting...')
            assert result == 'SUN is already running'

    def test_stop_when_not_running(self):
        tools = Manager()
        with patch.object(tools, 'daemon_status', return_value=False):
            result = tools.daemon_process('stop', 'Stopping...')
            assert result == 'SUN is not running'

    def test_restart_when_not_running(self):
        tools = Manager()
        with patch.object(tools, 'daemon_status', return_value=False):
            result = tools.daemon_process('restart', 'Restarting...')
            assert result == 'SUN is not running'

    def test_start_success(self):
        tools = Manager()
        with patch.object(tools, 'daemon_status', return_value=False):
            with patch.object(tools, '_daemon_start'):
                result = tools.daemon_process('start', 'Starting...')
                assert result == 'Starting...'

    def test_start_failure(self):
        tools = Manager()
        with patch.object(tools, 'daemon_status', return_value=False):
            with patch.object(tools, '_daemon_start', side_effect=OSError('error')):
                result = tools.daemon_process('start', 'Starting...')
                assert result.startswith('FAILED')

    def test_stop_success(self):
        tools = Manager()
        with patch.object(tools, 'daemon_status', return_value=True):
            with patch.object(tools, '_daemon_stop'):
                result = tools.daemon_process('stop', 'Stopping...')
                assert result == 'Stopping...'

    def test_restart_success(self):
        tools = Manager()
        with patch.object(tools, 'daemon_status', return_value=True):
            with patch.object(tools, '_daemon_stop'):
                with patch.object(tools, '_daemon_start'):
                    with patch('time.sleep'):
                        result = tools.daemon_process('restart', 'Restarting...')
                        assert result == 'Restarting...'
