import json
import re
import unittest

from jc.parsers.bluetoothctl import (
    _controller_head_pattern,
    _controller_line_pattern,
    _device_head_pattern,
    _device_line_pattern,
    _parse_controller,
    _parse_device,
    Controller,
    Device,
    parse,
)


class BluetoothctlTests(unittest.TestCase):
    def test_bluetoothctl_nodata(self):
        """
        Test 'bluetoothctl' with no data
        """

        output=''
        self.assertEqual(parse(output, quiet=True), [])

    def test_bluetoothctl_invalid_call(self):
        """
        Test 'bluetoothctl' with output from invalid call
        """

        output='Invalid command in menu main: foo'
        self.assertEqual(parse(output, quiet=True), [])

    def test_bluetoothctl_with_invalid_args(self):
        """
        Test 'bluetoothctl' with output from invalid arguments
        """

        output='Too many arguments: 2 > 1'
        self.assertEqual(parse(output, quiet=True), [])

    def test_bluetoothctl_no_controller(self):
        """
        Test 'bluetoothctl' with no controller
        """

        output='No default controller available'
        self.assertEqual(parse(output, quiet=True), [])


    def test_bluetoothctl_no_controller_found(self):
        """
        Test 'bluetoothctl' with no controller found
        """

        output='Controller EB:06:EF:62:B3:33 not available'
        self.assertEqual(parse(output, quiet=True), [])

    def test_bluetoothctl_no_device_found(self):
        """
        Test 'bluetoothctl' with no device found
        """

        output='Device EB:06:EF:62:B3:33 not available'
        self.assertEqual(parse(output, quiet=True), [])

    def test_bluetoothctl_controller(self):
        """
        Test 'bluetoothctl' with controller
        """

        with open("tests/fixtures/generic/bluetoothctl_controller.out", "r") as f:
            output = f.read()

        actual = parse(output, quiet=True)

        self.assertIsNotNone(actual)
        self.assertIsNotNone(actual[0])

        expected = {
            "address": "CC:BB:AF:27:6A:E4",
            "is_public": True,
            "name": "arch",
            "alias": "arch",
            "class": "0x006c010c",
            "powered": "yes",
            "discoverable": "no",
            "discoverable_timeout": "0x000000b4",
            "pairable": "no",
            "uuids": [
                "Handsfree                 (0000111e-0000-1000-8000-00805f9b34fb)",
                "Audio Source              (0000110a-0000-1000-8000-00805f9b34fb)",
                "Audio Sink                (0000110b-0000-1000-8000-00805f9b34fb)",
                "PnP Information           (00001200-0000-1000-8000-00805f9b34fb)",
                "A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)",
                "A/V Remote Control        (0000110e-0000-1000-8000-00805f9b34fb)",
                "Handsfree Audio Gateway   (0000111f-0000-1000-8000-00805f9b34fb)"
            ],
            "modalias": "usb:v1D6Bp0246d0542",
            "discovering": "no"
        }

        if actual:
            for k, v in expected.items():
                self.assertEqual(v, actual[0][k], f"Controller regex failed on {k}")

    def test_bluetoothctl_controller_2(self):
        """
        Test 'bluetoothctl' with controller 2
        """

        with open("tests/fixtures/generic/bluetoothctl_controller_2.out", "r") as f:
            output = f.read()

        actual = parse(output, quiet=True)

        self.assertIsNotNone(actual)
        self.assertIsNotNone(actual[0])

        expected = {
            "address": "CC:52:AF:17:6A:E4",
            "is_public": True,
            "name": "starbase",
            "alias": "starbase",
            "class": "0x006c010c (7078156)",
            "powered": "yes",
            "power_state": "on",
            "discoverable": "no",
            "discoverable_timeout": "0x000000b4 (180)",
            "pairable": "no",
            "uuids": [
                "Handsfree                 (0000111e-0000-1000-8000-00805f9b34fb)",
                "Audio Source              (0000110a-0000-1000-8000-00805f9b34fb)",
                "Audio Sink                (0000110b-0000-1000-8000-00805f9b34fb)",
                "PnP Information           (00001200-0000-1000-8000-00805f9b34fb)",
                "A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)",
                "A/V Remote Control        (0000110e-0000-1000-8000-00805f9b34fb)",
                "Handsfree Audio Gateway   (0000111f-0000-1000-8000-00805f9b34fb)"
            ],
            "modalias": "usb:v1D6Bp0246d054F",
            "discovering": "no"
        }

        if actual:
            for k, v in expected.items():
                self.assertEqual(v, actual[0][k], f"Controller regex failed on {k}")

    def test_bluetoothctl_controller_with_manufacturer(self):
        """
        Test 'bluetoothctl' with controller having manufacturer attr
        """

        with open("tests/fixtures/generic/bluetoothctl_controller_with_manufacturer.out", "r") as f:
            output = f.read()

        actual = parse(output, quiet=True)

        self.assertIsNotNone(actual)
        self.assertIsNotNone(actual[0])

        expected = {
            "manufacturer": "0x0002 (2)",
            "version": "0x08 (8)",
            "address": "48:A4:72:3C:96:63",
            "is_public": True,
            "name": "ubuntu",
            "alias": "ubuntu",
            "class": "0x007c0104 (8126724)",
            "powered": "yes",
            "discoverable": "yes",
            "discoverable_timeout": "0x000000b4 (180)",
            "pairable": "yes",
            "uuids": [
                "Message Notification Se.. (00001133-0000-1000-8000-00805f9b34fb)",
                "A/V Remote Control        (0000110e-0000-1000-8000-00805f9b34fb)",
                "OBEX Object Push          (00001105-0000-1000-8000-00805f9b34fb)",
                "Message Access Server     (00001132-0000-1000-8000-00805f9b34fb)",
                "PnP Information           (00001200-0000-1000-8000-00805f9b34fb)",
                "IrMC Sync                 (00001104-0000-1000-8000-00805f9b34fb)",
                "Vendor specific           (00005005-0000-1000-8000-0002ee000001)",
                "A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)",
                "Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)",
                "Phonebook Access Server   (0000112f-0000-1000-8000-00805f9b34fb)",
                "Audio Sink                (0000110b-0000-1000-8000-00805f9b34fb)",
                "Device Information        (0000180a-0000-1000-8000-00805f9b34fb)",
                "Generic Access Profile    (00001800-0000-1000-8000-00805f9b34fb)",
                "Handsfree Audio Gateway   (0000111f-0000-1000-8000-00805f9b34fb)",
                "Audio Source              (0000110a-0000-1000-8000-00805f9b34fb)",
                "OBEX File Transfer        (00001106-0000-1000-8000-00805f9b34fb)",
                "Handsfree                 (0000111e-0000-1000-8000-00805f9b34fb)"
            ],
            "modalias": "usb:v1D6Bp0246d0548",
            "discovering": "yes"
        }

        if actual:
            for k, v in expected.items():
                self.assertEqual(v, actual[0][k], f"Controller regex failed on {k}")

    def test_bluetoothctl_controllers(self):
        """
        Test 'bluetoothctl' with controllers
        """

        output='Controller CC:52:AF:A4:6A:E4 arch [default]\n'
        output+='Controller CC:53:AF:17:6A:34 logi'

        actual = parse(output, quiet=True)

        self.assertIsNotNone(actual)
        self.assertIsNotNone(actual[0])
        self.assertIsNotNone(actual[1])

        expected = [
            {
                "address": "CC:52:AF:A4:6A:E4",
                "is_default": True,
                "name": "arch"
            },
            {
                "address": "CC:53:AF:17:6A:34",
                "name": "logi"
            },
        ]

        if actual:
            for k, v in expected[0].items():
                self.assertEqual(v, actual[0][k], f"Controller regex failed on {k}")

            for k, v in expected[1].items():
                self.assertEqual(v, actual[1][k], f"Controller regex failed on {k}")

    def test_bluetoothctl_device(self):
        """
        Test 'bluetoothctl' with device
        """

        with open("tests/fixtures/generic/bluetoothctl_device.out", "r") as f:
            output = f.read()

        actual = parse(output, quiet=True)

        self.assertIsNotNone(actual)
        self.assertIsNotNone(actual[0])

        expected = {
            "address": "EB:06:EF:62:B3:19",
            "is_public": True,
            "name": "TaoTronics TT-BH026",
            "alias": "TaoTronics TT-BH026",
            "class": "0x00240404",
            "icon": "audio-headset",
            "paired": "no",
            "bonded": "no",
            "trusted": "no",
            "blocked": "no",
            "connected": "no",
            "legacy_pairing": "no",
            "uuids": [
                "Advanced Audio Distribu.. (0000110d-0000-1000-8000-00805f9b34fb)",
                "Audio Sink                (0000110b-0000-1000-8000-00805f9b34fb)",
                "A/V Remote Control        (0000110e-0000-1000-8000-00805f9b34fb)",
                "A/V Remote Control Cont.. (0000110f-0000-1000-8000-00805f9b34fb)",
                "Handsfree                 (0000111e-0000-1000-8000-00805f9b34fb)",
                "Headset                   (00001108-0000-1000-8000-00805f9b34fb)",
                "Headset HS                (00001131-0000-1000-8000-00805f9b34fb)"
            ],
            "rssi": -52,
            "txpower": 4
        }

        if actual:
            for k, v in expected.items():
                self.assertEqual(v, actual[0][k], f"Device regex failed on {k}")
    def test_bluetoothctl_device_with_battery(self):
        """
        Test 'bluetoothctl' with device that has a battery
        """

        with open("tests/fixtures/generic/bluetoothctl_device_with_battery.out", "r") as f:
            output = f.read()

        actual = parse(output, quiet=True)

        self.assertIsNotNone(actual)
        self.assertIsNotNone(actual[0], actual)

        expected = {
            "name": "WH-1000XM3",
            "is_public": True,
            "is_random": False,
            "address": "67:F6:B4:0E:5C:94",
            "alias": "WH-1000XM3",
            "appearance": "",
            "class": "0x11240404 (2360324)",
            "icon": "audio-headset",
            "paired": "yes",
            "bonded": "yes",
            "trusted": "yes",
            "blocked": "no",
            "connected": "yes",
            "legacy_pairing": "no",
            "rssi": 0,
            "txpower": 0,
            "uuids": [
                "Vendor specific           (fd096fad-eed7-4504-943b-5fa1c0e761b2)",
                "Vendor specific           (03c57488-f7b6-45a3-8a23-ed4a890075cd)",
                "Vendor specific           (77a369ae-e453-4ff7-bc84-dc8f411eaa6a)",
                "Vendor specific           (8c274bd0-e7bd-4ed0-a391-55465e38005c)"
            ],
            "modalias": "usb:v052Cp0DC3d1426",
            "battery_percentage": 70
        }

        if actual:
            for k, v in expected.items():
                self.assertEqual(v, actual[0][k], f"Device regex failed on {k}")

    def test_bluetoothctl_device_random(self):
        """
        Test 'bluetoothctl' with device random
        """

        with open("tests/fixtures/generic/bluetoothctl_device_random.out", "r") as f:
            output = f.read()

        actual = parse(output, quiet=True)

        self.assertIsNotNone(actual)
        self.assertIsNotNone(actual[0])

        expected = {
            "address": "DF:1C:C3:B4:1A:1F",
            "is_random": True,
            "name": "M585/M590",
            "alias": "M585/M590",
            "appearance": "0x03c2",
            "icon": "input-mouse",
            "paired": "yes",
            "bonded": "yes",
            "trusted": "no",
            "blocked": "no",
            "connected": "no",
            "legacy_pairing": "no",
            "uuids": [
                "Generic Access Profile    (00001800-0000-1000-8000-00805f9b34fb)",
                "Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)",
                "Device Information        (0000180a-0000-1000-8000-00805f9b34fb)",
                "Battery Service           (0000180f-0000-1000-8000-00805f9b34fb)",
                "Human Interface Device    (00001812-0000-1000-8000-00805f9b34fb)",
                "Vendor specific           (00010000-0000-1000-8000-011f2000046d)"
            ],
            "modalias": "usb:v046DpB01Bd0011"
        }

        if actual:
            for k, v in expected.items():
                self.assertEqual(v, actual[0][k], f"Device regex failed on {k}")

    def test_bluetoothctl_devices(self):
        """
        Test 'bluetoothctl' with devices
        """

        output='Device EB:06:EF:62:13:19 TaoTronics TT-BH026\n'
        output+='Device AC:1F:EA:F8:AA:A1 wacom'

        actual = parse(output, quiet=True)

        self.assertIsNotNone(actual)
        self.assertIsNotNone(actual[0])
        self.assertIsNotNone(actual[1])

        expected = [
            {
                "address": "EB:06:EF:62:13:19",
                "name": "TaoTronics TT-BH026"
            },
            {
                "address": "AC:1F:EA:F8:AA:A1",
                "name": "wacom"
            }
        ]

        if actual:
            for k, v in expected[0].items():
                self.assertEqual(v, actual[0][k], f"Device regex failed on {k}")

            for k, v in expected[1].items():
                self.assertEqual(v, actual[1][k], f"Device regex failed on {k}")

    def test_bluetoothctl_devices_w_cable_pairing(self):
        """
        Test 'bluetoothctl' with devices with CablePairing field
        """
        data =  '''Device 3C:B0:ED:C4:8F:32 (public)
	Name: Nothing Ear
	Alias: Nothing Ear
	Class: 0x00240404 (2360324)
	Icon: audio-headset
	Paired: yes
	Bonded: yes
	Trusted: yes
	Blocked: no
	Connected: yes
	LegacyPairing: no
	CablePairing: no
	UUID: SDP                       (00000001-0000-1000-8000-00805f9b34fb)
	UUID: Serial Port               (00001101-0000-1000-8000-00805f9b34fb)
	UUID: Audio Sink                (0000110b-0000-1000-8000-00805f9b34fb)
	UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
	UUID: Advanced Audio Distribu.. (0000110d-0000-1000-8000-00805f9b34fb)
	UUID: A/V Remote Control        (0000110e-0000-1000-8000-00805f9b34fb)
	UUID: A/V Remote Control Cont.. (0000110f-0000-1000-8000-00805f9b34fb)
	UUID: Handsfree                 (0000111e-0000-1000-8000-00805f9b34fb)
	UUID: PnP Information           (00001200-0000-1000-8000-00805f9b34fb)
	UUID: Generic Audio             (00001203-0000-1000-8000-00805f9b34fb)
	UUID: Generic Access Profile    (00001800-0000-1000-8000-00805f9b34fb)
	UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
	UUID: Guangzhou SuperSound In.. (0000fd90-0000-1000-8000-00805f9b34fb)
	UUID: Google                    (0000fe2c-0000-1000-8000-00805f9b34fb)
	UUID: Vendor specific           (66666666-6666-6666-6666-666666666666)
	UUID: Vendor specific           (aeac4a03-dff5-498f-843a-34487cf133eb)
	UUID: Vendor specific           (df21fe2c-2515-4fdb-8886-f12c4d67927c)
	Modalias: bluetooth:v02B0p0000d001F
	Battery Percentage: 0x5f (95)
'''
        expected = [{"name":"Nothing Ear","is_public":True,"is_random":False,"address":"3C:B0:ED:C4:8F:32","alias":"Nothing Ear","appearance":"","class":"0x00240404 (2360324)","icon":"audio-headset","paired":"yes","bonded":"yes","trusted":"yes","blocked":"no","connected":"yes","legacy_pairing":"no","cable_pairing":"no","rssi":0,"txpower":0,"uuids":["SDP                       (00000001-0000-1000-8000-00805f9b34fb)","Serial Port               (00001101-0000-1000-8000-00805f9b34fb)","Audio Sink                (0000110b-0000-1000-8000-00805f9b34fb)","A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)","Advanced Audio Distribu.. (0000110d-0000-1000-8000-00805f9b34fb)","A/V Remote Control        (0000110e-0000-1000-8000-00805f9b34fb)","A/V Remote Control Cont.. (0000110f-0000-1000-8000-00805f9b34fb)","Handsfree                 (0000111e-0000-1000-8000-00805f9b34fb)","PnP Information           (00001200-0000-1000-8000-00805f9b34fb)","Generic Audio             (00001203-0000-1000-8000-00805f9b34fb)","Generic Access Profile    (00001800-0000-1000-8000-00805f9b34fb)","Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)","Guangzhou SuperSound In.. (0000fd90-0000-1000-8000-00805f9b34fb)","Google                    (0000fe2c-0000-1000-8000-00805f9b34fb)","Vendor specific           (66666666-6666-6666-6666-666666666666)","Vendor specific           (aeac4a03-dff5-498f-843a-34487cf133eb)","Vendor specific           (df21fe2c-2515-4fdb-8886-f12c4d67927c)"],"modalias":"bluetooth:v02B0p0000d001F","battery_percentage":95}]

        self.assertEqual(parse(data, quiet=True), expected)


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