from __future__ import annotations

import logging
from typing import Sequence

import httpx

from gateway_core.application.ports.NodeAgentPort import NodeAgentPort

from gateway_core.application.ports.NodesPort import NodesPort
from gateway_core.domain.Reboot import RebootDto
from gateway_core.domain.SoftwareUpgrade import SoftwareUpgradeDto
from gateway_core.domain.node.NodeInfo import NodeType, NodeInfo
from gateway_core.domain.node.NodeSelector import NodeSelector

logger = logging.getLogger(__name__)


class HttpNodeAgentAdapter(NodeAgentPort):
    """
    Triggers actions on a remote gatewayNodeAgent via HTTP.

    Expects a node entry with endpoint:
      { "type": "http", "baseUrl": "http://10.2.0.12:8080" }

    Default API endpoints:
      POST {baseUrl}/v1/actions/upgrade
      POST {baseUrl}/v1/actions/reboot
    """

    def __init__(
        self,
        *,
        upgradeActionPath: str = "/v1/actions/upgrade",
        rebootActionPath: str = "/v1/actions/reboot",
        timeoutSeconds: float = 30.0,
        headers: dict[str, str] | None = None,
    ) -> None:
        self._upgradeActionPath = upgradeActionPath
        self._rebootActionPath = rebootActionPath
        self._timeout = timeoutSeconds
        self._headers = headers or {}

    async def startUpgrade(self, *, nodeInfo: NodeInfo, dto: SoftwareUpgradeDto) -> None:
        payload = dto.__dict__
        if "selector" in payload:
            del payload["selector"]
        await self._postAction(nodeInfo=nodeInfo, actionPath=self._upgradeActionPath, payload=payload, actionName="upgrade")

    async def triggerReboot(self, *, nodeInfo: NodeInfo, dto: RebootDto) -> None:
        payload = dto.__dict__
        if "selector" in payload:
            del payload["selector"]
        await self._postAction(nodeInfo=nodeInfo, actionPath=self._rebootActionPath, payload=payload, actionName="reboot")

    async def _postAction(
        self,
        *,
        nodeInfo: NodeInfo,
        actionPath: str,
        payload: dict,
        actionName: str,
    ) -> None:
        node = await self._resolveHttpNode(nodeInfo=nodeInfo)

        baseUrl = node.endpoint.baseUrl
        if not baseUrl:
            raise ValueError(f"HttpNodeAgentAdapter: node '{node.nodeId}' HTTP endpoint has no baseUrl")

        url = f"{baseUrl.rstrip('/')}{actionPath}"

        logger.info("HttpNodeAgentAdapter: POST %s action=%s payload=%s", url, actionName, payload)

        timeout = httpx.Timeout(self._timeout)

        try:
            async with httpx.AsyncClient(timeout=timeout, headers=self._headers) as client:
                resp = await client.post(url, json=payload)
        except httpx.HTTPError as ex:
            raise RuntimeError(
                f"HttpNodeAgentAdapter: {actionName} request failed for node '{node.nodeId}' "
                f"(HTTP client error: {ex.__class__.__name__}: {ex})"
            ) from ex

        if resp.status_code >= 400:
            body = (resp.text or "").strip()
            bodyPreview = body[:300]
            raise RuntimeError(
                f"HttpNodeAgentAdapter: {actionName} request failed for node '{node.nodeId}' "
                f"({resp.status_code}) body='{bodyPreview}'"
            )

        logger.info("HttpNodeAgentAdapter: %s triggered on node '%s' (%s)", actionName, node.nodeId, resp.status_code)

    async def _resolveHttpNode(self, *, nodeInfo: NodeInfo):
        if not nodeInfo:
            raise ValueError("HttpNodeAgentAdapter: unknown nodeInfo")

        if nodeInfo.endpoint is None:
            raise ValueError(f"HttpNodeAgentAdapter: node '{nodeInfo}' has no endpoint configured")

        if nodeInfo.endpoint.type != NodeType.HTTP:
            raise ValueError(
                f"HttpNodeAgentAdapter: node '{nodeInfo}' endpoint is not HTTP (got '{nodeInfo.endpoint.type.value}')"
            )

        return nodeInfo
