from __future__ import annotations

from typing import List

from .common import CommonDiagnosticSession
from ..automotive import DTCStatusMask, UdsNegativeResponse
from ..uds import (
    CommunicationControlType,
    CommunicationType,
    DiagnosticSessionType,
    DynamicallyDefineDataIdentifierDefinitionType,
    DTCSettingType,
    EcuResetType,
    NegativeResponseCode,
    ReadDTCReportType,
    RoutineControlType,
    ServiceId,
    TesterPresentType,
)
from ..vehical import VehiCALHelper


class UDSDiagnosticSession(CommonDiagnosticSession):
    default_session = DiagnosticSessionType.default

    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.vehical = VehiCALHelper()

    def handle_request(self, message):
        bytes_in = message.bytes
        if not bytes_in:
            raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
        response_bytes = [bytes_in[0] | 0x40]

        sid = bytes_in[0]
        if sid == ServiceId.clear_diagnostic_information:
            if len(bytes_in) < 4:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            group_of_dtc = int.from_bytes([0x00] + bytes_in[1:4], "big")
            if group_of_dtc != 0xFFFFFF:
                raise UdsNegativeResponse(NegativeResponseCode.request_out_of_range)
            response_bytes += self.clear_diagnostic_information(group_of_dtc)
            return response_bytes

        if sid == ServiceId.communication_control:
            if len(bytes_in) != 3:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            if bytes_in[1] not in CommunicationControlType._value2member_map_:
                raise UdsNegativeResponse(NegativeResponseCode.sub_function_not_supported)
            if bytes_in[2] not in CommunicationType._value2member_map_:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            response_bytes += self.communication_control(bytes_in[1], bytes_in[2])
            return response_bytes

        if sid == ServiceId.diagnostic_session_control:
            if len(bytes_in) != 2:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            if bytes_in[1] not in DiagnosticSessionType._value2member_map_:
                raise UdsNegativeResponse(NegativeResponseCode.sub_function_not_supported)
            response_bytes += self.session_control(bytes_in[1])
            return response_bytes

        if sid == ServiceId.control_dtc_setting:
            if len(bytes_in) < 2:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            if bytes_in[1] not in DTCSettingType._value2member_map_:
                raise UdsNegativeResponse(NegativeResponseCode.sub_function_not_supported)
            response_bytes += self.control_dtc_setting(bytes_in[1], bytes_in[2:])
            return response_bytes

        if sid == ServiceId.dynamically_define_data_identifier:
            if len(bytes_in) <= 1:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            if bytes_in[1] not in DynamicallyDefineDataIdentifierDefinitionType._value2member_map_:
                raise UdsNegativeResponse(NegativeResponseCode.sub_function_not_supported)
            identifier = 0x0000
            if len(bytes_in) >= 4:
                identifier = (bytes_in[2] << 8) | bytes_in[3]
            subfunction = bytes_in[1]
            if subfunction == DynamicallyDefineDataIdentifierDefinitionType.define_by_identifier:
                if len(bytes_in) < 8:
                    raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
                record_length = 4
                payload_length = len(bytes_in) - 4
                if payload_length % record_length != 0:
                    raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
                definitions = []
                index = 4
                while index + record_length <= len(bytes_in):
                    source_id = (bytes_in[index] << 8) | bytes_in[index + 1]
                    lower_bound = bytes_in[index + 2] - 1
                    if lower_bound < 0:
                        raise UdsNegativeResponse(NegativeResponseCode.request_out_of_range)
                    length = bytes_in[index + 3]
                    if length <= 0:
                        raise UdsNegativeResponse(NegativeResponseCode.request_out_of_range)
                    upper_bound = lower_bound + length
                    definitions.append((source_id, range(lower_bound, upper_bound)))
                    index += record_length
                response_bytes += self.dynamically_define_identifier(identifier, definitions)
                return response_bytes
            if subfunction == DynamicallyDefineDataIdentifierDefinitionType.define_by_memory_address:
                if len(bytes_in) <= 5:
                    raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
                format_spec = bytes_in[4]
                number_of_address_bytes = format_spec & 0x0F
                number_of_length_bytes = format_spec >> 4
                if number_of_address_bytes == 0 or number_of_length_bytes == 0:
                    raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
                index = 5
                slices = []
                record_length = number_of_address_bytes + number_of_length_bytes
                while index + record_length <= len(bytes_in):
                    address_bytes = bytes_in[index:index + number_of_address_bytes]
                    index += number_of_address_bytes
                    length_bytes = bytes_in[index:index + number_of_length_bytes]
                    index += number_of_length_bytes
                    address = int.from_bytes(bytes(address_bytes), "big")
                    length = int.from_bytes(bytes(length_bytes), "big")
                    if length <= 0:
                        raise UdsNegativeResponse(NegativeResponseCode.request_out_of_range)
                    block = next((b for b in self.memory if address >= b.address and address + length <= b.address + b.size), None)
                    if block is None:
                        raise UdsNegativeResponse(NegativeResponseCode.request_out_of_range)
                    offset = int(address - block.address)
                    slices.append(("memory", (block, offset, int(length))))
                if index != len(bytes_in):
                    raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
                if not slices:
                    raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
                response_bytes += self.register_dynamic_identifier(identifier, subfunction, slices)
                return response_bytes
            if subfunction == DynamicallyDefineDataIdentifierDefinitionType.clear:
                if len(bytes_in) not in (2, 4):
                    raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
                response_bytes += self.clear_dynamically_defined_identifier(identifier)
                return response_bytes

        if sid == ServiceId.ecu_reset:
            if len(bytes_in) != 2:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            if bytes_in[1] not in EcuResetType._value2member_map_:
                raise UdsNegativeResponse(NegativeResponseCode.sub_function_not_supported)
            response_bytes += self.reset(bytes_in[1])
            return response_bytes

        if sid == ServiceId.read_data_by_identifier:
            if len(bytes_in) < 3 or (len(bytes_in) - 1) % 2 != 0:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            index = 1
            while index + 1 < len(bytes_in):
                identifier = (bytes_in[index] << 8) | bytes_in[index + 1]
                response_bytes += [bytes_in[index], bytes_in[index + 1]]
                response_bytes += self.read_data_by(identifier)
                index += 2
            return response_bytes

        if sid == ServiceId.read_scaling_data_by_identifier:
            if len(bytes_in) < 3 or (len(bytes_in) - 1) % 2 != 0:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            index = 1
            while index + 1 < len(bytes_in):
                identifier = (bytes_in[index] << 8) | bytes_in[index + 1]
                response_bytes += [bytes_in[index], bytes_in[index + 1]]
                response_bytes += self.read_scaling_data_by(identifier)
                index += 2
            return response_bytes

        if sid == ServiceId.read_dtc_information:
            if len(bytes_in) < 3:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            if bytes_in[1] not in ReadDTCReportType._value2member_map_:
                raise UdsNegativeResponse(NegativeResponseCode.sub_function_not_supported)
            response_bytes.append(bytes_in[1])
            report_type = bytes_in[1]
            if report_type == ReadDTCReportType.report_dtc_by_status_mask:
                status_mask = DTCStatusMask(bytes_in[2])
                response_bytes += self.read_dtc_by_status_mask(status_mask)
                return response_bytes
            raise UdsNegativeResponse(NegativeResponseCode.sub_function_not_supported)

        if sid == ServiceId.read_memory_by_address:
            if len(bytes_in) < 4:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            format_spec = bytes_in[1]
            number_of_length_bytes = format_spec >> 4
            number_of_address_bytes = format_spec & 0x0F
            expected = 2 + number_of_address_bytes + number_of_length_bytes
            if len(bytes_in) != expected:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            address = bytes_in[2:2 + number_of_address_bytes]
            length = bytes_in[2 + number_of_address_bytes:expected]
            response_bytes += self.read_memory_by(address, length)
            return response_bytes

        if sid == ServiceId.read_data_by_periodic_identifier:
            if len(bytes_in) < 2:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            transmission_mode = bytes_in[1]
            if transmission_mode not in (0x00, 0x01, 0x02, 0x03):
                raise UdsNegativeResponse(NegativeResponseCode.sub_function_not_supported)
            if len(bytes_in) > 2 and (len(bytes_in) - 2) % 2 != 0:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            response_bytes.append(transmission_mode)
            index = 2
            while index + 1 < len(bytes_in):
                identifier = (bytes_in[index] << 8) | bytes_in[index + 1]
                response_bytes += [bytes_in[index], bytes_in[index + 1]]
                index += 2
            return response_bytes

        if sid == ServiceId.request_download:
            if len(bytes_in) < 5 or len(bytes_in) > 33:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            data_format = bytes_in[1]
            compression = data_format >> 4
            encryption = data_format & 0x0F
            format_spec = bytes_in[2]
            number_of_length_bytes = format_spec >> 4
            number_of_address_bytes = format_spec & 0x0F
            expected = 3 + number_of_address_bytes + number_of_length_bytes
            if len(bytes_in) != expected:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            address = bytes_in[3:3 + number_of_address_bytes]
            length = bytes_in[3 + number_of_address_bytes:expected]
            response_bytes += self.request_download(compression, encryption, address, length)
            return response_bytes

        if sid == ServiceId.request_upload:
            if len(bytes_in) < 5 or len(bytes_in) > 33:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            data_format = bytes_in[1]
            compression = data_format >> 4
            encryption = data_format & 0x0F
            format_spec = bytes_in[2]
            number_of_length_bytes = format_spec >> 4
            number_of_address_bytes = format_spec & 0x0F
            expected = 3 + number_of_address_bytes + number_of_length_bytes
            if len(bytes_in) != expected:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            address = bytes_in[3:3 + number_of_address_bytes]
            length = bytes_in[3 + number_of_address_bytes:expected]
            response_bytes += self.request_upload(compression, encryption, address, length)
            return response_bytes

        if sid == ServiceId.request_transfer_exit:
            response_bytes += self.request_transfer_exit(bytes_in[1:])
            return response_bytes

        if sid == ServiceId.input_output_control_by_identifier:
            if len(bytes_in) < 4:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            identifier = (bytes_in[1] << 8) | bytes_in[2]
            control_record = bytes_in[3:]
            if identifier not in self.identifiers:
                raise UdsNegativeResponse(NegativeResponseCode.request_out_of_range)
            id_obj = self.identifiers[identifier]
            if id_obj.write_levels and self.security_level not in id_obj.write_levels:
                raise UdsNegativeResponse(NegativeResponseCode.security_access_denied)
            if control_record:
                id_obj.content = control_record
                self.identifiers[identifier] = id_obj
            response_bytes += [bytes_in[1], bytes_in[2]] + control_record
            return response_bytes

        if sid == ServiceId.routine_control:
            if len(bytes_in) < 4:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            if bytes_in[1] not in RoutineControlType._value2member_map_:
                raise UdsNegativeResponse(NegativeResponseCode.sub_function_not_supported)
            identifier = (bytes_in[2] << 8) | bytes_in[3]
            option_record = bytes_in[4:]
            response_bytes += [bytes_in[1], bytes_in[2], bytes_in[3]]
            response_bytes += self.routine_control(bytes_in[1], identifier, option_record)
            return response_bytes

        if sid == ServiceId.security_access:
            if self.current == DiagnosticSessionType.default:
                raise UdsNegativeResponse(NegativeResponseCode.service_not_supported_in_active_session)
            if len(bytes_in) < 2:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            level = bytes_in[1]
            if level < 0x80:
                if level % 2 == 1:
                    response_bytes += self.security_access_request_seed(level)
                else:
                    if len(bytes_in) < 3:
                        raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
                    response_bytes += self.security_access_send_key(level, bytes_in[2:])
            else:
                if any(s.level == level for s in self.security_levels):
                    response_bytes += self.security_access_request_seed(level)
                elif any(s.level == level - 1 for s in self.security_levels):
                    if len(bytes_in) < 3:
                        raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
                    response_bytes += self.security_access_send_key(level, bytes_in[2:])
                else:
                    raise UdsNegativeResponse(NegativeResponseCode.request_out_of_range)
            return response_bytes

        if sid == ServiceId.tester_present:
            if len(bytes_in) >= 2:
                if bytes_in[1] == 0x30:
                    if len(bytes_in) not in (2, 3):
                        raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
                    self.reset_default_session_timer()
                    response_bytes += self.vehical.ident()
                    return response_bytes
                if bytes_in[1] == 0x32:
                    self.reset_default_session_timer()
                    response_bytes += self.vehical.write_memory(bytes_in)
                    return response_bytes
                if bytes_in[1] == 0x33:
                    self.reset_default_session_timer()
                    response_bytes += self.vehical.read_fast_array(bytes_in)
                    return response_bytes
            if len(bytes_in) == 1:
                response_bytes += self.tester_present(TesterPresentType.send_response)
                return response_bytes
            if len(bytes_in) == 2:
                if bytes_in[1] not in TesterPresentType._value2member_map_:
                    raise UdsNegativeResponse(NegativeResponseCode.sub_function_not_supported)
                response_bytes += self.tester_present(bytes_in[1])
                return [] if len(response_bytes) == 1 else response_bytes
            raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)

        if sid == ServiceId.transfer_data:
            if len(bytes_in) < 2:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            block_number = bytes_in[1]
            response_bytes += self.transfer_data(block_number, bytes_in[2:])
            return response_bytes

        if sid == ServiceId.write_data_by_identifier:
            if len(bytes_in) < 4:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            identifier = (bytes_in[1] << 8) | bytes_in[2]
            response_bytes += self.write_data_by(identifier, bytes_in[3:])
            return response_bytes

        if sid == ServiceId.write_memory_by_address:
            if len(bytes_in) < 5:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            format_spec = bytes_in[1]
            number_of_length_bytes = format_spec >> 4
            number_of_address_bytes = format_spec & 0x0F
            header_length = 2 + number_of_address_bytes + number_of_length_bytes
            if len(bytes_in) < header_length:
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            address = bytes_in[2:2 + number_of_address_bytes]
            length = bytes_in[2 + number_of_address_bytes:header_length]
            data = bytes_in[header_length:]
            response_bytes += self.write_memory_by(address, length, data)
            return response_bytes

        raise UdsNegativeResponse(NegativeResponseCode.service_not_supported)

    def configure(self, property_name: str, value: str) -> None:
        if property_name == "vin":
            identifier = self.identifiers.get(0xF190)
            if identifier:
                identifier.content = list(value.encode("utf-8"))
                self.identifiers[0xF190] = identifier
            return
        if not self.vehical.configure(property_name, value):
            super().configure(property_name, value)

    def read_dtc_by_status_mask(self, status_mask: DTCStatusMask) -> List[int]:
        self.reset_default_session_timer()
        combined_mask = DTCStatusMask.none
        dtc_bytes: List[int] = []
        for dtc in self.dtcs:
            if dtc.state & status_mask:
                dtc_bytes.extend(dtc.bytes)
                dtc_bytes.append(int(dtc.state))
                combined_mask |= dtc.state
        return [int(combined_mask)] + dtc_bytes
