"""Unit test for the metrics library.

This test is primarily intended to ensure the metrics codepaths for V1
and V2 devices are executed in case folks working on the codebase have one
type of unit and not the other.

This test is a little gross; it forcibly injects a dict that looks a lot
like unmarshalled JSON from the device into libdyson's handlers. We then
check for the values on the far-side of the Prometheus metrics, which
ensures things are hooked up right, but limits our ability to test. For
example, we cannot currently test enum values with this test.
"""


import enum
import unittest

from prometheus_client import registry
import libdyson

import metrics


NAME = 'test device'
SERIAL = 'XX1-ZZ-1234ABCD'
CREDENTIALS = 'credz'


class TestMetrics(unittest.TestCase):
    def setUp(self):
        self.registry = registry.CollectorRegistry(auto_describe=True)
        self.metrics = metrics.Metrics(registry=self.registry)

    def test_enum_values(self):
        test = enum.Enum('testEnum', 'RED GREEN BLUE')
        self.assertEqual(metrics.enum_values(test), [1, 2, 3])

    def test_update_v1_environmental(self):
        device = libdyson.DysonPureCoolLink(
            SERIAL, CREDENTIALS, libdyson.DEVICE_TYPE_PURE_COOL_LINK)
        payload = {
            'msg': 'ENVIRONMENTAL-CURRENT-SENSOR-DATA',
            'time': '2021-03-17T15:09:23.000Z',
            'data': {'tact': '2956', 'hact': '0047', 'pact': '0005', 'vact': 'INIT', 'sltm': 'OFF'}
        }
        device._handle_message(payload)

        labels = {'name': NAME, 'serial': SERIAL}
        self.metrics.update(NAME, device, is_state=False,
                            is_environmental=True)

        cases = {
            'dyson_temperature_celsius': 22.6,
            'dyson_volatile_organic_compounds_units': 0,
            'dyson_dust_units': 5
        }
        for metric, want in cases.items():
            got = self.registry.get_sample_value(metric, labels)
            self.assertEqual(got, want, f'metric {metric}')

    def test_update_v2_environmental(self):
        device = libdyson.DysonPureCool(
            SERIAL, CREDENTIALS, libdyson.DEVICE_TYPE_PURE_COOL)
        payload = {
            'msg': 'ENVIRONMENTAL-CURRENT-SENSOR-DATA',
            'time': '2021-03-17T15:09:23.000Z',
            'data': {'tact': '2956', 'hact': '0047', 'pm10': '3', 'pm25': 'INIT',
                     'noxl': 30, 'va10': 'INIT', 'sltm': 'OFF'}
        }
        device._handle_message(payload)

        labels = {'name': NAME, 'serial': SERIAL}
        self.metrics.update(NAME, device, is_state=False,
                            is_environmental=True)

        cases = {
            'dyson_temperature_celsius': 22.6,
            'dyson_pm25_units': 0,
            'dyson_pm10_units': 3,
            'dyson_volatile_organic_compounds_units': 0,
            'dyson_nitrogen_oxide_units': 3
        }
        for metric, want in cases.items():
            got = self.registry.get_sample_value(metric, labels)
            self.assertEqual(got, want, f'metric {metric}')

    def test_update_formaldehyde_environmental(self):
        device = libdyson.DysonPureCoolFormaldehyde(
            SERIAL, CREDENTIALS, libdyson.DEVICE_TYPE_PURE_COOL_FORMALDEHYDE)
        payload = {
            'msg': 'ENVIRONMENTAL-CURRENT-SENSOR-DATA',
            'time': '2021-03-17T15:09:23.000Z',
            'data': {'tact': '2956', 'hact': '0047', 'pm10': '3', 'pm25': 'INIT',
                     'noxl': 30, 'va10': 'INIT', 'hcho': '0002', 'hchr': '0003',
                     'sltm': 'OFF'}
        }
        device._handle_message(payload)

        labels = {'name': NAME, 'serial': SERIAL}
        self.metrics.update(NAME, device, is_state=False,
                            is_environmental=True)

        cases = {
            'dyson_formaldehyde_units': 2,
        }
        for metric, want in cases.items():
            got = self.registry.get_sample_value(metric, labels)
            self.assertEqual(got, want, f'metric {metric}')

    def test_update_v1_state(self):
        device = libdyson.DysonPureHotCoolLink(
            SERIAL, CREDENTIALS, libdyson.DEVICE_TYPE_PURE_HOT_COOL_LINK)
        payload = {
            'msg': 'STATE-CHANGE',
            'time': '2021-03-17T15:27:30.000Z',
            'mode-reason': 'PRC',
            'state-reason': 'MODE',
            'product-state': {
                'fmod': ['AUTO', 'FAN'],
                'fnst': ['FAN', 'FAN'],
                'fnsp': ['AUTO', 'AUTO'],
                'qtar': ['0003', '0003'],
                'oson': ['ON', 'ON'],
                'rhtm': ['ON', 'ON'],
                'filf': ['2209', '2209'],
                'ercd': ['NONE', 'NONE'],
                'nmod': ['OFF', 'OFF'],
                'wacd': ['NONE', 'NONE'],
                'hmod': ['OFF', 'OFF'],
                'hmax': ['2960', '2960'],
                'hsta': ['OFF', 'OFF'],
                'ffoc': ['ON', 'ON'],
                'tilt': ['OK', 'OK']},
            'scheduler': {'srsc': 'a58d', 'dstv': '0001', 'tzid': '0001'}}
        device._handle_message(payload)

        labels = {'name': NAME, 'serial': SERIAL}
        self.metrics.update(NAME, device, is_state=True,
                            is_environmental=False)

        cases = {
            'dyson_fan_speed_units': -1,
            'dyson_filter_life_seconds': 2209 * 60 * 60,
            'dyson_quality_target_units': 3,
            'dyson_heat_target_celsius': 23,
        }
        # We can't currently test Enums, so we skip those for now and only evaluate gauges.
        for metric, want in cases.items():
            got = self.registry.get_sample_value(metric, labels)
            self.assertEqual(got, want, f'metric {metric}')

    def test_update_v2_state(self):
        device = libdyson.DysonPureHotCool(
            SERIAL, CREDENTIALS, libdyson.DEVICE_TYPE_PURE_HOT_COOL)
        payload = {
            'msg': 'STATE-CHANGE',
            'time': '2021-03-17T15:27:30.000Z',
            'mode-reason': 'PRC',
            'state-reason': 'MODE',
            'product-state': {
                'auto': ['ON', 'ON'],
                'fpwr': ['ON', 'ON'],
                'fmod': ['AUTO', 'FAN'],
                'fnst': ['FAN', 'FAN'],
                'fnsp': ['AUTO', 'AUTO'],
                'nmdv': ['0002', '0002'],
                'oson': ['ON', 'ON'],
                'oscs': ['ON', 'ON'],
                'osal': ['0136', '0136'],
                'osau': ['0226', '0226'],
                'rhtm': ['ON', 'ON'],
                'cflr': ['0055', '0055'],
                'hflr': ['0097', '0097'],
                'cflt': ['SCOF', 'SCOF'],
                'hflt': ['GCOM', 'GCOM'],
                'ercd': ['NONE', 'NONE'],
                'nmod': ['OFF', 'OFF'],
                'wacd': ['NONE', 'NONE'],
                'hmod': ['OFF', 'OFF'],
                'hmax': ['2960', '2960'],
                'hsta': ['OFF', 'OFF'],
                'fdir': ['ON', 'ON']}}
        device._handle_message(payload)

        labels = {'name': NAME, 'serial': SERIAL}
        self.metrics.update(NAME, device, is_state=True,
                            is_environmental=False)

        # We can't currently test Enums, so we skip those for now and only evaluate gauges.
        cases = {
            'dyson_fan_speed_units': -1,
            'dyson_carbon_filter_life_percent': 55,
            'dyson_hepa_filter_life_percent': 97,
            'dyson_heat_target_celsius': 23,
        }
        for metric, want in cases.items():
            got = self.registry.get_sample_value(metric, labels)
            self.assertEqual(got, want, f'metric {metric}')


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