from __future__ import annotations

import logging
from dataclasses import dataclass, field
from typing import Dict, List, Optional

from .automotive import UdsNegativeResponse
from .uds import NegativeResponseCode

logger = logging.getLogger("ecumulator.vehical")


class VehiCALHelper:
    default_version = 0x0101
    default_maximum_buffer = 0x03FF
    default_recommended_buffer = 0x008A
    default_base_address = 0x5003B000

    def __init__(self) -> None:
        self.context = Context()

    def ident(self) -> List[int]:
        base = self.context.ensure_base_address()
        return [
            (self.context.version >> 8) & 0xFF,
            self.context.version & 0xFF,
            (self.context.maximum_buffer_size >> 8) & 0xFF,
            self.context.maximum_buffer_size & 0xFF,
            (self.context.recommended_buffer_size >> 8) & 0xFF,
            self.context.recommended_buffer_size & 0xFF,
        ] + list(base.to_bytes(4, "big"))

    def write_memory(self, request: List[int]) -> List[int]:
        if len(request) < 8:
            raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
        address = int.from_bytes(bytes(request[2:6]), "big")
        length = (request[6] << 8) | request[7]
        if length <= 0 or len(request) != 8 + length:
            raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
        data = request[8:8 + length]
        base = self.context.ensure_base_address()
        if address < base:
            raise UdsNegativeResponse(NegativeResponseCode.request_out_of_range)
        self.context.resize_buffer_if_needed()
        self.context.write(data, address)
        written = length
        return [(written >> 8) & 0xFF, written & 0xFF]

    def read_fast_array(self, request: List[int]) -> List[int]:
        if len(request) != 6:
            raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
        address = int.from_bytes(bytes(request[2:6]), "big")
        definition = self.definition(address)
        entries = self.parse(definition)
        if not entries:
            raise UdsNegativeResponse(NegativeResponseCode.conditions_not_correct)
        payload: List[int] = []
        for entry in entries:
            payload.extend(self.sample_bytes(entry))
        return payload

    def configure(self, property_name: str, value: str) -> bool:
        if property_name == "vehical.baseAddress":
            parsed = parse_integer(value)
            if parsed is not None:
                self.context.base_address = parsed
            return True
        if property_name == "vehical.maximumBuffer":
            parsed = parse_integer(value)
            if parsed is not None:
                self.context.maximum_buffer_size = parsed
            return True
        if property_name == "vehical.recommendedBuffer":
            parsed = parse_integer(value)
            if parsed is not None:
                self.context.recommended_buffer_size = parsed
            return True
        if property_name == "vehical.version":
            parsed = parse_integer(value)
            if parsed is not None:
                self.context.version = parsed
            return True
        return False

    def definition(self, address: int) -> List[int]:
        data = self.context.read(address)
        if not data or data[-1] != 0x00:
            logger.debug("definition missing terminator")
            raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
        return data

    def parse(self, definition: List[int]) -> List["Entry"]:
        index = 0
        entries: List[Entry] = []
        while index < len(definition):
            count = definition[index]
            index += 1
            if count == 0:
                break
            if index + 2 > len(definition):
                raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
            prefix = (definition[index] << 8) | definition[index + 1]
            index += 2
            for _ in range(count):
                if index + 3 > len(definition):
                    raise UdsNegativeResponse(NegativeResponseCode.incorrect_message_length_or_invalid_format)
                length = definition[index]
                lower = (definition[index + 1] << 8) | definition[index + 2]
                index += 3
                if length <= 0:
                    raise UdsNegativeResponse(NegativeResponseCode.request_out_of_range)
                address = (prefix << 16) | lower
                entries.append(Entry(address=address, length=length))
        return entries

    def sample_bytes(self, entry: "Entry") -> List[int]:
        bytes_out: List[int] = []
        for offset in range(entry.length):
            absolute = entry.address + offset
            value = (absolute & 0xFF) ^ ((absolute >> 8) & 0xFF)
            bytes_out.append(value & 0xFF)
        return bytes_out


@dataclass
class Entry:
    address: int
    length: int


@dataclass
class Context:
    version: int = VehiCALHelper.default_version
    maximum_buffer_size: int = VehiCALHelper.default_maximum_buffer
    recommended_buffer_size: int = VehiCALHelper.default_recommended_buffer
    base_address: Optional[int] = VehiCALHelper.default_base_address
    buffer: List[int] = field(default_factory=list)
    defined_ranges: Dict[int, int] = field(default_factory=dict)

    def __post_init__(self) -> None:
        if not self.buffer:
            self.buffer = [0x00] * self.maximum_buffer_size

    def resize_buffer_if_needed(self) -> None:
        desired = max(1, int(self.maximum_buffer_size))
        if len(self.buffer) != desired:
            self.buffer = [0x00] * desired

    def ensure_base_address(self) -> int:
        if self.base_address is None:
            self.base_address = VehiCALHelper.default_base_address
        return self.base_address

    def offset_for(self, address: int) -> Optional[int]:
        base = self.base_address
        if base is None or address < base:
            return None
        delta = address - base
        if delta < 0:
            return None
        return delta

    def write(self, data: List[int], address: int) -> None:
        offset = self.offset_for(address)
        if offset is None:
            raise UdsNegativeResponse(NegativeResponseCode.request_out_of_range)
        end = offset + len(data)
        self.resize_buffer_if_needed()
        if end > len(self.buffer):
            raise UdsNegativeResponse(NegativeResponseCode.request_out_of_range)
        for idx, byte in enumerate(data):
            self.buffer[offset + idx] = byte
        self.defined_ranges[address] = len(data)

    def read(self, address: int) -> List[int]:
        offset = self.offset_for(address)
        if offset is None:
            raise UdsNegativeResponse(NegativeResponseCode.request_out_of_range)
        length = self.defined_ranges.get(address)
        if not length:
            raise UdsNegativeResponse(NegativeResponseCode.conditions_not_correct)
        end = offset + length
        if end > len(self.buffer):
            raise UdsNegativeResponse(NegativeResponseCode.conditions_not_correct)
        return list(self.buffer[offset:end])


def parse_integer(value: str) -> int | None:
    trimmed = value.strip()
    if not trimmed or trimmed.startswith("-"):
        return None
    lower = trimmed.lower()
    if lower.startswith("0x"):
        digits = lower[2:]
        radix = 16
    else:
        digits = trimmed
        radix = 10
    try:
        return int(digits, radix)
    except ValueError:
        return None
