"""
Unit tests for the handler files.
"""

import json
import os
from unittest.mock import MagicMock
import pytest

from handler_library.handler import PayloadHandler
from handler_library.battery_handler import BatteryHandler
from handler_library.personal_handler import PersonalHandler
from handler_library.measurement_handler import MeasurementHandler
from handler_library.text_handler import TextHandler
from handler_library.data_handler import DataHandler
from NativeUI import NativeUI

BATTERY_JSON = json.load(
    open(
        os.environ["PYTHONPATH"].split(os.pathsep)[0]
        + "/tests/unittests/fixtures/batterySample.json"
    )
)
CYCLE_JSON = json.load(
    open(
        os.environ["PYTHONPATH"].split(os.pathsep)[0]
        + "/tests/unittests/fixtures/cycleSample.json"
    )
)
PERSONAL_JSON = json.load(
    open(
        os.environ["PYTHONPATH"].split(os.pathsep)[0]
        + "/tests/unittests/fixtures/personalSample.json"
    )
)
READBACK_JSON = json.load(
    open(
        os.environ["PYTHONPATH"].split(os.pathsep)[0]
        + "/tests/unittests/fixtures/readbackSample.json"
    )
)
TEST_JSON = json.load(
    open(
        os.environ["PYTHONPATH"].split(os.pathsep)[0]
        + "/tests/unittests/fixtures/testSample.json"
    )
)

DATA_JSON = json.load(
    open(
        os.environ["PYTHONPATH"].split(os.pathsep)[0]
        + "/tests/unittests/fixtures/dataSample.json"
    )
)

ENGLISH_JSON = json.load(
    open(os.environ["PYTHONPATH"].split(os.pathsep)[0] + "/configs/text_english.json")
)

PORTUGUESE_JSON = json.load(
    open(
        os.environ["PYTHONPATH"].split(os.pathsep)[0] + "/configs/text_portuguese.json"
    )
)


class TestPayloadHandler:
    """
    Tests for the PayloadHandler base class.
    """

    handler = PayloadHandler(["TEST"])
    handler.active_payload = MagicMock(return_value=True)

    def test_handler_set_db(self):
        """
        Check that the handler set_db works only for the payload type(s) set in the
        initialiser
        """
        assert (
            self.handler.set_db(TEST_JSON) == 0
        ), "hander set_db does not return 0 for valid payload."
        for payload in [BATTERY_JSON, CYCLE_JSON, PERSONAL_JSON, READBACK_JSON]:
            assert (
                self.handler.set_db(payload) == 1
            ), "handler does not return 1 for invalid payload type"

    def test_handler_get_db(self):
        assert (
            self.handler.get_db() == TEST_JSON["TEST"]
        ), "handler get_db returns differing information to that set."

    def test_handler_active_payload_called(self):
        assert (
            self.handler.active_payload() is True
        ), "active_payload was not called when set_db was run."

    def test_handler_clear_database(self):
        assert self.handler.clear_db() == 0, "handler clear_db did not return 0."
        test_db = self.handler.get_db()
        assert (
            test_db == {}
        ), "handler get_db did not return an empty dict after clear_db - returned %s" % test_db 


class TestBatteryHandler:
    """
    Tests the battery_handler logic by giving active_payload a sample battery payload
    (NativeUI/tests/unittests/fixtures/batterySample.json) with a known output status
    (NativeUI/tests/unittests/fixtures/battery_status_output_sample.json).
    """

    battery_handler = BatteryHandler()

    def test_battery_set_db_returns_0_for_battery_payload(self):
        assert (
            self.battery_handler.set_db(BATTERY_JSON) == 0
        ), "battery_handler does not return 0 for valid battery payload"
        for payload in [CYCLE_JSON, PERSONAL_JSON, READBACK_JSON, TEST_JSON]:
            assert (
                self.battery_handler.set_db(payload) == 1
            ), "battery_handler does not return 1 for invalid payload type"

    def test_battery_handler_compute_battery_percent(self):
        """
        compute_battery_percent must return 0 if bat85 is 0, and 1 if bat85 is one.
        """
        batt_per_1 = {"bat85": 1}
        batt_per_0 = {"bat85": 0}
        assert self.battery_handler.compute_battery_percent(batt_per_1) == 85.0
        assert self.battery_handler.compute_battery_percent(batt_per_0) == 0.0

    def test_battery_handler_emits_UpdateBatteryDisplay_signal_on_active_payload(self):
        """
        Create a mock_display method that we connect the UpdateBatteryDisplay signal to.
        Test whether the mock_display method is called when the battery handler's
        active_payload method is called.
        """
        display_updated = False

        def mock_display(new_status: dict):
            nonlocal display_updated
            display_updated = True

        self.battery_handler.UpdateBatteryDisplay.connect(mock_display)
        self.battery_handler.active_payload(BATTERY_JSON)
        assert (
            display_updated is True
        ), "battery handler did not emit UpdateDisplay signal"

    def test_battery_handler_new_status_set_correctly(self):
        """
        Iterate over the test cases in "/tests/unittests/fixtures/batteryTestCases.json"
        confirming that in each case the new_status constructed by the handler matches
        the expected output.
        """
        testcases = json.load(
            open(
                os.environ["PYTHONPATH"].split(os.pathsep)[0]
                + "/tests/unittests/fixtures/batteryTestCases.json"
            )
        )

        for label, payload, expected_status in zip(
            testcases["label"], testcases["payload"], testcases["status"]
        ):
            correct_new_status = False

            def mock_display(new_status: dict):
                nonlocal correct_new_status
                if new_status == expected_status:
                    correct_new_status = True

            self.battery_handler.UpdateBatteryDisplay.connect(mock_display)
            self.battery_handler.set_db(payload)
            assert correct_new_status is True, (
                "new_status did not match expected status for case '%s'" % label
            )


class TestPersonalHandler:
    """
    Tests the personal_handler logic by giving active_payload a sample personal payload
    (NativeUI/tests/unittests/fixtures/personalSample.json) with a known output
    status (NativeUI/tests/unittests/fixtures/personal_status_output_sample.json).
    """

    personal_handler = PersonalHandler()

    def test_personal_set_db_returns_0_for_personal_payload(self):
        assert (
            self.personal_handler.set_db(PERSONAL_JSON) == 0
        ), "personal_handler does not return 0 for valid battery payload"
        for payload in [CYCLE_JSON, BATTERY_JSON, READBACK_JSON, TEST_JSON]:
            assert (
                self.personal_handler.set_db(payload) == 1
            ), "personal_handler does not return 1 for invalid payload type"

    def test_personal_handler_emits_UpdatePersonalDisplay_signal_on_active_payload(
        self
    ):
        """
        Create a mock_display method that we connect the UpdatePersonalDisplay signal to.
        Test whether the mock_display method is called when the personal handler's
        active_payload method is called.
        """
        display_updated = False

        def mock_display(new_status: dict):
            nonlocal display_updated
            display_updated = True

        self.personal_handler.UpdatePersonalDisplay.connect(mock_display)
        self.personal_handler.active_payload(PERSONAL_JSON)
        assert (
            display_updated is True
        ), "personal handler did not emit UpdateDisplay signal"


class TestMeasurementHandler:
    """
    Tests the measurement_handler logic by giving active_payload samples cycle and readback payload
    (NativeUI/tests/unittests/fixtures/cycleSample.json) and
    (NativeUI/tests/unittests/fixtures/readbackSample.json) with a known output status
    (NativeUI/tests/unittests/fixtures/measurement_status_output_sample.json).
    """

    measurement_handler = MeasurementHandler()

    def test_cycle_set_db_returns_0_for_cycle_payload(self):
        assert (
            self.measurement_handler.set_db(CYCLE_JSON) == 0
        ), "personal_handler does not return 0 for valid battery payload"
        for payload in [PERSONAL_JSON, BATTERY_JSON, READBACK_JSON, TEST_JSON]:
            if payload == READBACK_JSON:
                assert self.measurement_handler.set_db(READBACK_JSON) == 0
            else:
                assert (
                    self.measurement_handler.set_db(payload) == 1
                ), "personal_handler does not return 1 for invalid payload type"

    def test_readback_set_db_returns_0_for_readback_payload(self):
        assert (
            self.measurement_handler.set_db(READBACK_JSON) == 0
        ), "personal_handler does not return 0 for valid battery payload"
        for payload in [PERSONAL_JSON, BATTERY_JSON, CYCLE_JSON, TEST_JSON]:
            if payload == CYCLE_JSON:
                assert self.measurement_handler.set_db(CYCLE_JSON) == 0
            else:
                assert (
                    self.measurement_handler.set_db(payload) == 1
                ), "personal_handler does not return 1 for invalid payload type"

    def test_measurement_handler_emits_UpdateMeasurements_cycle_signal_on_active_payload(
        self
    ):
        """
        Create a mock_display method that we connect the UpdatePersonalDisplay signal to.
        Test whether the mock_display method is called when the personal handler's
        active_payload method is called.
        """
        display_updated = False

        def mock_display(new_status: dict):
            nonlocal display_updated
            display_updated = True

        self.measurement_handler.UpdateMeasurements.connect(mock_display)
        self.measurement_handler.active_payload(CYCLE_JSON)
        assert (
            display_updated is True
        ), "personal handler did not emit UpdateDisplay signal"

    def test_measurement_handler_emits_UpdateMeasurements_readback_signal_on_active_payload(
        self
    ):
        """
        Create a mock_display method that we connect the UpdatePersonalDisplay signal to.
        Test whether the mock_display method is called when the personal handler's
        active_payload method is called.
        """
        display_updated = False

        def mock_display(new_status: dict):
            nonlocal display_updated
            display_updated = True

        self.measurement_handler.UpdateMeasurements.connect(mock_display)
        self.measurement_handler.active_payload(READBACK_JSON)
        assert (
            display_updated is True
        ), "personal handler did not emit UpdateDisplay signal"


class TestTextHandler:
    """
    Tests the text_handler logic by giving active_payload samples
    (NativeUI/configs/text_english.json) and
    (NativeUI/configs/text_portuguese.json) list of localisations samples
    """

    cfg_files_path = "/home/pi/hev/NativeUI/configs/"
    localisation_files = [
        cfg_files_path + "text_english.json",
        cfg_files_path + "text_portuguese.json",
    ]
    text_handler = TextHandler(localisation_files)

    def test_text_handler_returns_FileNotFoundError_for_wrong_language_file(self):

        SPANISH_JSON = "/home/pi/hev/NativeUI/configs/text_spanish.json"

        with pytest.raises(FileNotFoundError):
            text_handler1 = TextHandler(SPANISH_JSON)

    def test_text_handler_emits_UpdateText_on_data_set(self):
        """
        Create a mock_display method that we connect the UpdateText signal to.
        Test whether the mock_display method is called when the personal handler's
        active_payload method is called.
        """
        display_updated = False

        def mock_display(new_status: dict):
            nonlocal display_updated
            display_updated = True

        self.text_handler.UpdateText.connect(mock_display)
        self.text_handler.on_data_set()
        assert (
            display_updated is True
        ), "personal handler did not emit UpdateDisplay signal"

    def test_set_location_path_returns_FileNotFoundError_for_wrong_path(self):

        local_PATH = "/home/pi/hev/NativeUI/configs/"
        with pytest.raises(FileNotFoundError):
            localisation = TextHandler(local_PATH)

    def test_next_config_returns_0_when_index_not_a_list(self):

        index = self.text_handler.next_config()

        check_index = isinstance(index, list)


class TestDataHandler:

    data_handler = DataHandler()

    def test_data_set_db_returns_0_for_data_payload(self):
        assert (
            self.data_handler.set_db(DATA_JSON) == 0
        ), "personal_handler does not return 0 for valid data payload"
        for payload in [PERSONAL_JSON, BATTERY_JSON, READBACK_JSON, TEST_JSON]:
            assert (
                self.data_handler.set_db(payload) == 1
            ), "data_handler does not return 1 for invalid payload type"

    def test_data_handler_emits_UpdatePlots_on_send_update_plots_signal(self):
        """
        Create a mock_display method that we connect the UpdatePlots signal to.
        Test whether the mock_display method is called when the data handler's
        send_update_plots_signal method is called.
        """
        display_updated = False
        plots_database = self.data_handler._DataHandler__plots_database

        def mock_display(new_status: dict):
            nonlocal display_updated
            display_updated = True

        self.data_handler.UpdatePlots.connect(mock_display)
        self.data_handler.send_update_plots_signal()
        assert display_updated is True, "data handler did not emit UpdatePlots signal"
        assert plots_database["timestamp"][-1] == 0
        assert plots_database["pressure"][-1] == 16.122142791748047
        assert plots_database["flow"][-1] == 34.1150016784668
        assert plots_database["volume"][-1] == 46869.6953125

