from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Callable, Mapping, MutableMapping

JSON_BODY_REQUIRED = "JSON body is required"


@dataclass(frozen=True)
class PayloadMappingResult:
    payload: dict[str, Any]
    missing_fields: tuple[str, ...] = ()
    error: str | None = None


@dataclass(frozen=True)
class ModuleVersionSchema:
    required_fields: tuple[str, ...] = field(default_factory=tuple)
    inbound_aliases: Mapping[str, str] = field(default_factory=dict)
    outbound_aliases: Mapping[str, str] = field(default_factory=dict)
    field_normalizers: Mapping[str, callable[[Any], Any]] = field(default_factory=dict)

    def normalize(self, payload: Mapping[str, Any]) -> dict[str, Any]:
        normalized = _normalize_structure(payload, self.inbound_aliases)
        if not self.field_normalizers:
            return normalized
        for key, normalizer in self.field_normalizers.items():
            if key in normalized:
                try:
                    normalized[key] = normalizer(normalized.get(key))
                except Exception:
                    normalized[key] = normalized.get(key)
        return normalized

    def serialize(self, payload: Any) -> Any:
        return _serialize_structure(payload, self.outbound_aliases)


MODULE_SUPPORT_MATRIX: dict[str, dict[str, bool]] = {
    "2.1.1": {
        "chargingprofiles": False,
    },
    "2.2": {},
    "2.3": {},
}

COMMON_INBOUND_ALIASES: dict[str, str] = {
    "locationId": "location_id",
    "evseId": "evse_uid",
    "evse_id": "evse_uid",
    "connectorId": "connector_id",
    "authorizationId": "authorization_id",
    "ChargingProfile": "charging_profile",
    "chargingProfile": "charging_profile",
}


def _upper_str(value: Any) -> Any:
    if isinstance(value, str):
        return value.strip().upper()
    return value


def _normalize_token_type(value: Any) -> str:
    upper = _upper_str(value)
    return upper or "RFID"


MODULE_SCHEMAS: dict[str, dict[str, ModuleVersionSchema]] = {
    "locations": {
        "default": ModuleVersionSchema(
            required_fields=("id",),
            inbound_aliases={**COMMON_INBOUND_ALIASES},
        ),
    },
    "sessions": {
        "default": ModuleVersionSchema(inbound_aliases=COMMON_INBOUND_ALIASES),
    },
    "cdrs": {
        "default": ModuleVersionSchema(inbound_aliases=COMMON_INBOUND_ALIASES),
    },
    "tariffs": {
        "default": ModuleVersionSchema(inbound_aliases=COMMON_INBOUND_ALIASES),
    },
    "tokens": {
        "default": ModuleVersionSchema(
            required_fields=("uid", "type", "auth_id", "issuer", "last_updated"),
            inbound_aliases={
                **COMMON_INBOUND_ALIASES,
                "authId": "auth_id",
                "visualNumber": "visual_number",
                "contractId": "contract_id",
                "whitelistStatus": "whitelist",
                "validUntil": "valid_until",
            },
            outbound_aliases={
                "visual_number": "visual_number",
                "contract_id": "contract_id",
                "auth_id": "auth_id",
            },
            field_normalizers={
                "type": _normalize_token_type,
                "whitelist": _upper_str,
                "status": _upper_str,
            },
        ),
    },
    "commands": {
        "default": ModuleVersionSchema(inbound_aliases=COMMON_INBOUND_ALIASES),
    },
    "chargingprofiles": {
        "default": ModuleVersionSchema(inbound_aliases=COMMON_INBOUND_ALIASES),
    },
}


def module_is_supported(version: str, module: str) -> bool:
    return MODULE_SUPPORT_MATRIX.get(version, {}).get(module, True)


def map_incoming_payload(
    module: str,
    version: str,
    payload: Any,
    *,
    validate_required: bool = True,
) -> PayloadMappingResult:
    if not isinstance(payload, Mapping):
        return PayloadMappingResult({}, (JSON_BODY_REQUIRED,), error=JSON_BODY_REQUIRED)

    schema = _schema_for(module, version)
    normalized = schema.normalize(payload)

    missing: tuple[str, ...] = ()
    if validate_required and schema.required_fields:
        missing_list = [field for field in schema.required_fields if _is_missing_field(normalized, field)]
        missing = tuple(missing_list)

    return PayloadMappingResult(normalized, missing)


def map_outgoing_payload(module: str, version: str, payload: Any) -> Any:
    if payload is None:
        return None
    schema = _schema_for(module, version)
    return schema.serialize(payload)


def _schema_for(module: str, version: str) -> ModuleVersionSchema:
    module_schemas = MODULE_SCHEMAS.get(module, {})
    if version in module_schemas:
        return module_schemas[version]
    if "default" in module_schemas:
        return module_schemas["default"]
    return ModuleVersionSchema()


def _normalize_structure(value: Any, aliases: Mapping[str, str]) -> Any:
    if isinstance(value, Mapping):
        normalized: MutableMapping[str, Any] = {}
        for key, item in value.items():
            normalized_key = aliases.get(key, key)
            normalized[normalized_key] = _normalize_structure(item, aliases)
        return normalized
    if isinstance(value, list):
        return [_normalize_structure(item, aliases) for item in value]
    return value


def _serialize_structure(value: Any, aliases: Mapping[str, str]) -> Any:
    if isinstance(value, Mapping):
        serialized: MutableMapping[str, Any] = {}
        for key, item in value.items():
            target_key = aliases.get(key, key)
            serialized[target_key] = _serialize_structure(item, aliases)
        return serialized
    if isinstance(value, list):
        return [_serialize_structure(item, aliases) for item in value]
    return value


def _is_missing_field(payload: Mapping[str, Any], field: str) -> bool:
    if field not in payload:
        return True
    value = payload.get(field)
    return value in (None, "")
