"""NEOedgeX side of the existing Chamber ``POST /v1/enroll/device`` seam."""

from __future__ import annotations

import json
import re
import urllib.error
import urllib.request
from dataclasses import dataclass

from .canonical import canonical_bytes
from .identity import DeviceIdentity

DEVICE_ENROLL_DOMAIN = b"NEO-CHAMBER:DEVICE-ENROLL:V1"


class EnrollmentError(ValueError):
    pass


@dataclass(frozen=True)
class DeviceEnrollment:
    member_id: str
    edge_id: str
    install_version: str
    install_hash: str
    capabilities: tuple[str, ...] = ("neomark",)

    def document(self, identity: DeviceIdentity) -> dict:
        if not all(isinstance(value, str) and value for value in
                   (self.member_id, self.edge_id, self.install_version, self.install_hash)):
            raise EnrollmentError("DEVICE_ENROLL_FIELDS_REQUIRED")
        if re.fullmatch(r"[0-9a-f]{64}", self.install_hash) is None:
            raise EnrollmentError("INSTALL_HASH_MUST_BE_SHA256_HEX")
        return {"member_id": self.member_id, "device_pub_hex": identity.public_hex,
                "edge_id": self.edge_id, "install_version": self.install_version,
                "install_hash": self.install_hash,
                "capabilities": sorted(set(self.capabilities))}

    def signed(self, identity: DeviceIdentity) -> dict:
        document = self.document(identity)
        signature = identity.signer(DEVICE_ENROLL_DOMAIN + canonical_bytes(document)).hex()
        return {**document, "signature_hex": signature}


def bind_device(url: str, enrollment: DeviceEnrollment, identity: DeviceIdentity,
                timeout_s: float = 15.0) -> dict:
    body = canonical_bytes(enrollment.signed(identity))
    request = urllib.request.Request(url.rstrip("/") + "/v1/enroll/device", data=body,
                                     headers={"Content-Type": "application/json"}, method="POST")
    try:
        with urllib.request.urlopen(request, timeout=timeout_s) as response:
            answer = json.loads(response.read())
            if response.status != 200 or not isinstance(answer, dict) \
                    or answer.get("member_id") != enrollment.member_id \
                    or not answer.get("decision_ref") or not answer.get("device_ref"):
                raise EnrollmentError("CHAMBER_DEVICE_BIND_READBACK_INVALID")
            return answer
    except urllib.error.HTTPError as error:
        raise EnrollmentError(f"CHAMBER_DEVICE_BIND_REFUSED:{error.code}") from error
    except (urllib.error.URLError, OSError, ValueError) as error:
        raise EnrollmentError(f"CHAMBER_DEVICE_BIND_UNREACHABLE:{error}") from error
