"""Sidero signature generation for Omni API calls."""

from __future__ import annotations

import base64
import hashlib
import json
import time
from collections.abc import Mapping, Sequence
from dataclasses import dataclass

from omni.auth.service_account import ServiceAccount

SIGNATURE_HEADER = "x-sidero-signature"
TIMESTAMP_HEADER = "x-sidero-timestamp"
PAYLOAD_HEADER = "x-sidero-payload"
SIGNATURE_VERSION = "siderov1"
AUTHORIZATION_HEADER = "authorization"

INCLUDED_GRPC_HEADERS: Sequence[str] = (
    TIMESTAMP_HEADER,
    "nodes",
    "selectors",
    "fieldSelectors",
    "runtime",
    "context",
    "cluster",
    "namespace",
    "uid",
    AUTHORIZATION_HEADER,
)


@dataclass(slots=True, frozen=True)
class SignedHeaders:
    headers: dict[str, str]
    payload: str


class SideroSigner:
    def __init__(self, account: ServiceAccount) -> None:
        self._account = account

    @property
    def identity(self) -> str:
        return self._account.name

    @property
    def fingerprint(self) -> str:
        return self._account.fingerprint

    def _signature_header_value(self, payload: str) -> str:
        key = self._account.private_key
        signature_bin: bytes

        if hasattr(key, "sign_detached"):
            signature_bin = bytes(key.sign_detached(payload.encode("utf-8")))
        else:
            signature = key.sign(payload.encode("utf-8"), detached=True)
            signature_bin = bytes(signature)

        signature_base64 = base64.b64encode(signature_bin).decode("ascii")
        return f"{SIGNATURE_VERSION} {self.identity} {self.fingerprint} {signature_base64}"

    def sign_api_request(
        self,
        method_path: str,
        metadata: Mapping[str, str] | None = None,
    ) -> SignedHeaders:
        ts = str(int(time.time()))
        normalized: dict[str, list[str] | None] = {}

        for key in INCLUDED_GRPC_HEADERS:
            if key == TIMESTAMP_HEADER:
                normalized[key] = [ts]
                continue

            if metadata and key in metadata:
                normalized[key] = [metadata[key]]
            else:
                # Match go-api-signature payload semantics:
                # absent metadata headers are encoded as JSON null, not [].
                normalized[key] = None

        payload = json.dumps(
            {
                "headers": normalized,
                "method": method_path,
            },
            separators=(",", ":"),
            sort_keys=False,
        )

        signature_header = self._signature_header_value(payload)

        headers = {
            f"Grpc-Metadata-{TIMESTAMP_HEADER}": ts,
            f"Grpc-Metadata-{PAYLOAD_HEADER}": payload,
            f"Grpc-Metadata-{SIGNATURE_HEADER}": signature_header,
        }

        return SignedHeaders(headers=headers, payload=payload)

    def sign_image_request(self, http_method: str, request_uri: str, body: bytes = b"") -> SignedHeaders:
        ts = str(int(time.time()))
        body_sha = hashlib.sha256(body).hexdigest()
        payload = "\n".join([http_method.upper(), request_uri, ts, body_sha])
        signature_header = self._signature_header_value(payload)
        return SignedHeaders(
            headers={
                TIMESTAMP_HEADER: ts,
                SIGNATURE_HEADER: signature_header,
            },
            payload=payload,
        )
