import os
import json
import tempfile
from unittest.mock import patch, MagicMock

from cli import main, _print_table


MOCK_SCAN_RESULTS = {
    'subnet': '192.168.1.0/24',
    'hosts': [
        {
            'ip': '192.168.1.1',
            'hostname': 'gateway',
            'state': 'up',
            'mac_address': 'AA:BB:CC:DD:EE:01',
            'vendor': 'Cisco',
            'open_ports': [],
            'protocols': [],
        },
        {
            'ip': '192.168.1.10',
            'hostname': 'web-server',
            'state': 'up',
            'mac_address': 'AA:BB:CC:DD:EE:10',
            'vendor': 'Dell',
            'open_ports': [{'port': 80, 'protocol': 'tcp', 'service': 'http'}],
            'protocols': ['tcp'],
        },
    ],
    'total_hosts': 2,
    'scan_time': 0,
}


def _mock_scanner():
    """Return a mock NetworkScanner that yields MOCK_SCAN_RESULTS."""
    mock = MagicMock()
    mock.validate_subnet.return_value = True
    mock.get_network_info.return_value = {
        'network_address': '192.168.1.0',
        'broadcast_address': '192.168.1.255',
        'netmask': '255.255.255.0',
        'total_addresses': 256,
        'usable_addresses': 254,
        'prefix_length': 24,
    }
    mock.scan_subnet.return_value = MOCK_SCAN_RESULTS
    return mock


class TestPrintTable:
    def test_prints_hosts(self, capsys):
        _print_table(MOCK_SCAN_RESULTS)
        out = capsys.readouterr().out

        assert '192.168.1.1' in out
        assert '192.168.1.10' in out
        assert 'gateway' in out
        assert 'server' in out

    def test_empty_results(self, capsys):
        _print_table({'hosts': []})
        out = capsys.readouterr().out

        assert 'No hosts found' in out


class TestCLI:
    @patch('cli.NetworkScanner')
    def test_basic_scan(self, mock_cls, capsys):
        mock_cls.return_value = _mock_scanner()
        main(['192.168.1.0/24'])
        out = capsys.readouterr().out

        assert 'Found 2 hosts' in out
        assert '192.168.1.1' in out

    @patch('cli.NetworkScanner')
    def test_invalid_subnet(self, mock_cls):
        mock = MagicMock()
        mock.validate_subnet.return_value = False
        mock_cls.return_value = mock

        try:
            main(['bad-subnet'])
        except SystemExit as e:
            assert e.code == 1

    @patch('cli.NetworkScanner')
    def test_image_svg(self, mock_cls):
        mock_cls.return_value = _mock_scanner()

        with tempfile.NamedTemporaryFile(suffix='.svg', delete=False) as f:
            path = f.name

        try:
            main(['192.168.1.0/24', '-i', path, '-q'])

            with open(path, 'r') as f:
                svg = f.read()
            assert svg.startswith('<svg')
            assert '192.168.1.1' in svg
        finally:
            os.unlink(path)

    @patch('cli.NetworkScanner')
    def test_image_png(self, mock_cls):
        mock_cls.return_value = _mock_scanner()

        with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as f:
            path = f.name

        try:
            main(['192.168.1.0/24', '-i', path, '-q'])

            with open(path, 'rb') as f:
                header = f.read(8)
            assert header[:4] == b'\x89PNG'
        finally:
            os.unlink(path)

    @patch('cli.NetworkScanner')
    def test_image_jpeg(self, mock_cls):
        mock_cls.return_value = _mock_scanner()

        with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as f:
            path = f.name

        try:
            main(['192.168.1.0/24', '-i', path, '-q'])

            with open(path, 'rb') as f:
                header = f.read(2)
            assert header == b'\xff\xd8'
        finally:
            os.unlink(path)

    @patch('cli.NetworkScanner')
    def test_json_output(self, mock_cls):
        mock_cls.return_value = _mock_scanner()

        with tempfile.NamedTemporaryFile(suffix='.json', delete=False) as f:
            path = f.name

        try:
            main(['192.168.1.0/24', '--json', path, '-q'])

            with open(path, 'r') as f:
                data = json.load(f)
            assert 'metadata' in data
            assert 'topology' in data
        finally:
            os.unlink(path)

    @patch('cli.NetworkScanner')
    def test_csv_output(self, mock_cls):
        mock_cls.return_value = _mock_scanner()

        with tempfile.NamedTemporaryFile(suffix='.csv', delete=False) as f:
            path = f.name

        try:
            main(['192.168.1.0/24', '--csv', path, '-q'])

            with open(path, 'r') as f:
                content = f.read()
            assert 'IP' in content
            assert '192.168.1.1' in content
        finally:
            os.unlink(path)

    @patch('cli.NetworkScanner')
    def test_quiet_suppresses_table(self, mock_cls, capsys):
        mock_cls.return_value = _mock_scanner()
        main(['192.168.1.0/24', '-q'])
        out = capsys.readouterr().out

        assert '192.168.1.1' not in out

    @patch('cli.NetworkScanner')
    def test_scan_type_flag(self, mock_cls):
        mock = _mock_scanner()
        mock_cls.return_value = mock
        main(['192.168.1.0/24', '-t', 'tcp', '-q'])

        mock.scan_subnet.assert_called_once_with('192.168.1.0/24', 'tcp')
