#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
# Copyright 2020-2023 NXP
#
# SPDX-License-Identifier: BSD-3-Clause

"""Module with Debug Authentication Challenge (DAC) Packet."""

import logging
from struct import calcsize, pack, unpack_from

from spsdk.dat.debug_credential import DebugCredential
from spsdk.exceptions import SPSDKValueError

logger = logging.getLogger(__name__)


class DebugAuthenticationChallenge:
    """Base class for DebugAuthenticationChallenge."""

    def __init__(
        self,
        version: str,
        socc: int,
        uuid: bytes,
        rotid_rkh_revocation: int,
        rotid_rkth_hash: bytes,
        cc_soc_pinned: int,
        cc_soc_default: int,
        cc_vu: int,
        challenge: bytes,
    ) -> None:
        """Initialize the DebugAuthenticationChallenge object.

        :param version: The string representing version: for RSA: 1.0, for ECC: 2.0, 2.1, 2.2
        :param socc: The SoC Class that this credential applies to
        :param uuid: The string representing the unique device identifier
        :param rotid_rkh_revocation: State of certificate revocation field
        :param rotid_rkth_hash: The hash of roth-meta data
        :param cc_soc_pinned: State of lock bits in the debugger configuration field
        :param cc_soc_default: State of the debugger configuration field
        :param cc_vu: The Vendor usage that the vendor has associated with this credential
        :param challenge: Randomly generated bytes from the target
        """
        self.version = version
        self.socc = socc
        self.uuid = uuid
        self.rotid_rkh_revocation = rotid_rkh_revocation
        self.rotid_rkth_hash = rotid_rkth_hash
        self.cc_soc_pinned = cc_soc_pinned
        self.cc_soc_default = cc_soc_default
        self.cc_vu = cc_vu
        self.challenge = challenge

    def info(self) -> str:
        """String representation of DebugCredential."""
        msg = f"Version                : {self.version}\n"
        msg += f"SOCC                   : {DebugCredential.get_socc_description(self.version, self.socc)}\n"
        msg += f"UUID                   : {self.uuid.hex().upper()}\n"
        msg += f"CC_VU                  : {self.cc_vu}\n"
        msg += f"ROTID_rkh_revocation   : {format(self.rotid_rkh_revocation, '08X')}\n"
        msg += f"ROTID_rkth_hash        : {self.rotid_rkth_hash.hex()}\n"
        msg += f"CC_soc_pinned          : {format(self.cc_soc_pinned, '08X')}\n"
        msg += f"CC_soc_default         : {format(self.cc_soc_default, '08X')}\n"
        msg += f"Challenge              : {self.challenge.hex()}\n"
        return msg

    def validate_against_dc(self, dc: DebugCredential) -> None:
        """Validate against Debug Credential file.

        :param dc: Debug Credential class to be validated by DAC
        :raises SPSDKValueError: In case of invalid configuration detected.
        """
        if self.version != dc.VERSION and self.socc not in [0x5254049C]:
            raise SPSDKValueError(
                f"DAC Verification failed: Invalid protocol version.\nDAC: {self.version}\nDC:  {dc.VERSION}"
            )
        if self.socc != dc.socc:
            raise SPSDKValueError(
                f"DAC Verification failed: Invalid SOCC.\nDAC: {self.socc}\nDC:  {dc.socc}"
            )
        if self.uuid != dc.uuid and dc.uuid != bytes(len(dc.uuid)):
            raise SPSDKValueError(
                f"DAC Verification failed: Invalid UUID.\nDAC: {self.uuid.hex()}\nDC:  {dc.uuid.hex()}"
            )
        # RKTH is not part of challenge for RW61x devices
        if self.socc != 0xA:
            dc_rotkh = dc.get_rotkh()
            if dc_rotkh and not all(
                self.rotid_rkth_hash[x] == dc_rotkh[x] for x in range(len(self.rotid_rkth_hash))
            ):
                raise SPSDKValueError(
                    f"DAC Verification failed: Invalid RoT Hash. \n"
                    f"DAC: {self.rotid_rkth_hash.hex()}\nDC:  {dc_rotkh.hex()}"
                )

    def export(self) -> bytes:
        """Exports the DebugAuthenticationChallenge into bytes."""
        data = pack("<2H", *[int(part) for part in self.version.split(".")])
        data += pack("<L", self.socc)
        data += self.uuid
        data += pack("<L", self.rotid_rkh_revocation)
        data += self.rotid_rkth_hash
        data += pack("<L", self.cc_soc_pinned)
        data += pack("<L", self.cc_soc_default)
        data += pack("<L", self.cc_vu)
        data += self.challenge
        return data

    @classmethod
    def parse(cls, data: bytes, offset: int = 0) -> "DebugAuthenticationChallenge":
        """Parse the data into a DebugAuthenticationChallenge.

        :param data: Raw data as bytes
        :param offset: Offset within the input data
        :return: DebugAuthenticationChallenge object
        """
        format_head = "<2HL16sL"
        version_major, version_minor, socc, uuid, rotid_rkh_revocation = unpack_from(
            format_head, data, offset
        )
        # Note: EdgeLock is always 256b SRKH - if P384 these are the first 256b of SHA384(SRKT)
        hash_length = 48 if (socc in [4, 6] and version_minor == 1 and version_major == 2) else 32

        format_tail = f"<{hash_length}s3L32s"
        (
            rotid_rkth_hash,
            cc_soc_pinned,
            cc_soc_default,
            cc_vu,
            challenge,
        ) = unpack_from(format_tail, data, offset + calcsize(format_head))
        return cls(
            version=f"{version_major}.{version_minor}",
            socc=socc,
            uuid=uuid,
            rotid_rkh_revocation=rotid_rkh_revocation,
            rotid_rkth_hash=rotid_rkth_hash,
            cc_soc_default=cc_soc_default,
            cc_soc_pinned=cc_soc_pinned,
            cc_vu=cc_vu,
            challenge=challenge,
        )
