from binascii import unhexlify
from copy import deepcopy

import unittest

from dlms_cosem.exceptions import DecryptionError
from dlms_cosem.protocol.xdlms import GeneralGlobalCipher
from dlms_cosem.security import SecurityControlField, encrypt

from dsmr_parser import telegram_specifications
from dsmr_parser.exceptions import ParseError
from dsmr_parser.parsers import TelegramParser
from test.example_telegrams import TELEGRAM_SAGEMCOM_T210_D_R


class TelegramParserEncryptedTest(unittest.TestCase):
    """ Test parsing of a DSML encypted DSMR v5.x telegram. """
    DUMMY_ENCRYPTION_KEY = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
    DUMMY_AUTHENTICATION_KEY = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

    def __generate_encrypted(self, security_suite=0, authenticated=True, encrypted=True):
        security_control = SecurityControlField(
            security_suite=security_suite, authenticated=authenticated, encrypted=encrypted
        )
        encryption_key = unhexlify(self.DUMMY_ENCRYPTION_KEY)
        authentication_key = unhexlify(self.DUMMY_AUTHENTICATION_KEY)
        system_title = "SYSTEMID".encode("ascii")
        invocation_counter = int.from_bytes(bytes.fromhex("10000001"), "big")
        plain_data = TELEGRAM_SAGEMCOM_T210_D_R.encode("ascii")

        encrypted = encrypt(
            security_control=security_control,
            key=encryption_key,
            auth_key=authentication_key,
            system_title=system_title,
            invocation_counter=invocation_counter,
            plain_text=plain_data,
        )

        full_frame = bytearray(GeneralGlobalCipher.TAG.to_bytes(1, "big", signed=False))
        full_frame.extend(len(system_title).to_bytes(1, "big", signed=False))
        full_frame.extend(system_title)
        full_frame.extend([0x82])  # Length of the following length bytes
        # https://github.com/pwitab/dlms-cosem/blob/739f81a58e5f07663a512d4a128851333a0ed5e6/dlms_cosem/a_xdr.py#L33

        security_control = security_control.to_bytes()
        invocation_counter = invocation_counter.to_bytes(4, "big", signed=False)
        full_frame.extend((len(encrypted)
                           + len(invocation_counter)
                           + len(security_control)).to_bytes(2, "big", signed=False))
        full_frame.extend(security_control)
        full_frame.extend(invocation_counter)
        full_frame.extend(encrypted)

        return full_frame

    def test_parse(self):
        parser = TelegramParser(telegram_specifications.SAGEMCOM_T210_D_R)
        result = parser.parse(self.__generate_encrypted().hex(),
                              self.DUMMY_ENCRYPTION_KEY,
                              self.DUMMY_AUTHENTICATION_KEY)
        self.assertEqual(len(result), 18)

    def test_damaged_frame(self):
        # If the frame is damaged decrypting fails (crc is technically not needed)
        parser = TelegramParser(telegram_specifications.SAGEMCOM_T210_D_R)

        generated = self.__generate_encrypted()
        generated[150] = 0x00
        generated = generated.hex()

        with self.assertRaises(DecryptionError):
            parser.parse(generated, self.DUMMY_ENCRYPTION_KEY, self.DUMMY_AUTHENTICATION_KEY)

    def test_plain(self):
        # If a plain request is parsed with "general_global_cipher": True it fails
        parser = TelegramParser(telegram_specifications.SAGEMCOM_T210_D_R)

        with self.assertRaises(Exception):
            parser.parse(TELEGRAM_SAGEMCOM_T210_D_R, self.DUMMY_ENCRYPTION_KEY, self.DUMMY_AUTHENTICATION_KEY)

    def test_general_global_cipher_not_specified(self):
        # If a GGC frame is detected but general_global_cipher is not set it fails
        parser = TelegramParser(telegram_specifications.SAGEMCOM_T210_D_R)
        parser = deepcopy(parser)  # We do not want to change the module value
        parser.telegram_specification['general_global_cipher'] = False

        with self.assertRaises(ParseError):
            parser.parse(self.__generate_encrypted().hex(), self.DUMMY_ENCRYPTION_KEY, self.DUMMY_AUTHENTICATION_KEY)

    def test_only_encrypted(self):
        # Not implemented by dlms_cosem
        parser = TelegramParser(telegram_specifications.SAGEMCOM_T210_D_R)

        only_auth = self.__generate_encrypted(0, authenticated=False, encrypted=True).hex()

        with self.assertRaises(ValueError):
            parser.parse(only_auth, self.DUMMY_ENCRYPTION_KEY)

    def test_only_auth(self):
        # Not implemented by dlms_cosem
        parser = TelegramParser(telegram_specifications.SAGEMCOM_T210_D_R)

        only_auth = self.__generate_encrypted(0, authenticated=True, encrypted=False).hex()

        with self.assertRaises(ValueError):
            parser.parse(only_auth, authentication_key=self.DUMMY_AUTHENTICATION_KEY)