from __future__ import annotations

import logging
import uuid
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Mapping

from hubject_client import (
    HubjectClient,
    HubjectClientError,
    HubjectConfigurationError,
    load_hubject_config,
)

logger = logging.getLogger(__name__)


@dataclass(slots=True)
class AuthorizationDecision:
    """Represents the outcome of a Plug & Charge authorization request."""

    authorized: bool
    status: str
    raw_response: Any | None = None
    error: str | None = None


class PnCAdapter:
    """Adapter that drives the ISO 15118 Plug & Charge handshake via Hubject."""

    def __init__(self, hubject_client: HubjectClient) -> None:
        self._hubject_client = hubject_client

    @classmethod
    def from_config(
        cls,
        config: Mapping[str, Any],
        *,
        config_dir: Path | None = None,
        certs_dir: Path | None = None,
    ) -> "PnCAdapter":
        """Instantiate the adapter from the project's configuration mapping."""

        hubject_cfg = load_hubject_config(
            config,
            config_dir=config_dir,
            certs_dir=certs_dir,
        )
        client = HubjectClient(hubject_cfg)
        return cls(client)

    async def pnc_authorize(self, contract_cert: Mapping[str, Any] | str) -> AuthorizationDecision:
        """Authorize a Plug & Charge request using Hubject's AuthorizeStart API."""

        identification = self._normalise_identification(contract_cert)
        if identification is None:
            return AuthorizationDecision(
                authorized=False,
                status="Invalid",  # OCPP-compatible fallback
                error="missing_contract_certificate",
            )

        request_payload = {
            "CPOPartnerSessionID": str(uuid.uuid4()),
            "PartnerProductID": "pnc-adapter",  # sandbox marker
            "Identification": identification,
        }

        try:
            response = await self._hubject_client.authorize_start(request_payload)
        except HubjectClientError as exc:
            return AuthorizationDecision(
                authorized=False,
                status="Rejected",
                raw_response={"status": exc.status, "message": str(exc)},
                error=str(exc),
            )
        except Exception as exc:  # pragma: no cover - defensive default
            logger.exception("PnC authorization failed unexpectedly")
            return AuthorizationDecision(
                authorized=False,
                status="Rejected",
                error=str(exc),
            )

        status = self._extract_status(response) or "Unknown"
        return AuthorizationDecision(
            authorized=status.lower() == "accepted",
            status=status,
            raw_response=response,
        )

    def _normalise_identification(
        self, contract_cert: Mapping[str, Any] | str
    ) -> Mapping[str, Any] | None:
        if isinstance(contract_cert, Mapping):
            if "Identification" in contract_cert:
                candidate = contract_cert.get("Identification")
                if isinstance(candidate, Mapping):
                    return candidate
            if "PlugAndChargeIdentification" in contract_cert:
                candidate = contract_cert.get("PlugAndChargeIdentification")
                if isinstance(candidate, Mapping):
                    return {"PlugAndChargeIdentification": candidate}
            if "iso15118CertificateHashData" in contract_cert:
                hash_data = contract_cert.get("iso15118CertificateHashData")
                if isinstance(hash_data, Mapping):
                    return {
                        "PlugAndChargeIdentification": {
                            "iso15118CertificateHashData": hash_data,
                        }
                    }
        elif isinstance(contract_cert, str):
            stripped = contract_cert.strip()
            if stripped:
                return {
                    "PlugAndChargeIdentification": {
                        "evcoId": stripped,
                    }
                }
        return None

    def _extract_status(self, payload: Any) -> str | None:
        if isinstance(payload, Mapping):
            for key in ("AuthorizationStatus", "authorizationStatus", "Status", "status"):
                value = payload.get(key)
                if isinstance(value, str) and value.strip():
                    return value.strip()
            for nested in payload.values():
                if isinstance(nested, Mapping):
                    nested_status = self._extract_status(nested)
                    if nested_status:
                        return nested_status
        if isinstance(payload, str):
            candidate = payload.strip()
            if candidate:
                return candidate
        return None


def load_adapter_from_config(
    config: Mapping[str, Any], *, config_dir: Path | None = None, certs_dir: Path | None = None
) -> PnCAdapter:
    """Helper that initialises the adapter while surfacing configuration errors."""

    try:
        return PnCAdapter.from_config(config, config_dir=config_dir, certs_dir=certs_dir)
    except HubjectConfigurationError:
        logger.info("PnC adapter disabled due to Hubject config issues", exc_info=True)
        raise
