import json

import logging
import os
from pathlib import Path
from typing import Awaitable, Callable

from gateway_core.application.orchestrators.RebootOrchestrator import RebootOrchestrator
from gateway_core.application.orchestrators.SoftwareUpgradeOrchestrator import SoftwareUpgradeOrchestrator
from gateway_core.application.ports.FileSystemPort import FileSystemPort
from gateway_core.application.ports.NodeAgentPort import NodeAgentPort
from gateway_core.application.ports.NodesPort import NodesPort
from gateway_core.application.ports.NotificationPort import NotificationPort
from gateway_core.application.usecases.FileListUseCase import FileListUseCase
from gateway_core.application.usecases.FileUpgradeUseCase import FileUpgradeUseCase
from gateway_core.application.usecases.FileUploadUseCase import FileUploadUseCase
from gateway_core.application.usecases.SoftwareUpgradeUseCase import SoftwareUpgradeUseCase
from gateway_core.application.usecases.SystemInfoUseCase import SystemInfoUseCase
from gateway_core.domain.FileSystemData import FileUploadDto, FileListDto, FileUpgradeDto
from gateway_core.application.usecases.RebootUseCase import RebootUseCase
from gateway_core.domain.Reboot import RebootDto, RebootType, RebootEntity
from gateway_core.domain.SoftwareUpgrade import SoftwareUpgradeDto
from gateway_core.domain.node.NodeSelector import NodeSelector
from gateway_core.utils.JsonshemaValidater import jsonSchemaValidationPath
from gateway_transport.http.FileTransferClient import FileTransferClient

logger = logging.getLogger(__name__)


_rpcRoutes: dict[str, Callable[..., Awaitable[None]]] = {}

def rpc(method: str) -> Callable:
    """Register a handler for an RPC method (e.g. upgradeFw)."""

    def decorator(fn: Callable[..., Awaitable[None]]) -> Callable[..., Awaitable[None]]:
        _rpcRoutes[method] = fn
        return fn

    return decorator



class RpcRouter:
    def __init__(self,
                 notifier:NotificationPort,
                 nodes: NodesPort,
                 nodeAgent:NodeAgentPort,
                 fileSystemAdapter:FileSystemPort,
                 fileTransferClient:FileTransferClient,
                 ) -> None:
        """
            Factory for the RPC router. Register RPC methods here.
            Keeps wiring out of the listener and avoids global state.
        """
        self.ECP_WORKING_DIR = os.environ.get("ECP_WORK_FILES_DIR", "")

        # needed use cases
        self._softwareUpgradeUseCase = SoftwareUpgradeUseCase(
            orchestrator=SoftwareUpgradeOrchestrator(nodes=nodes, nodeAgent=nodeAgent),
            notifier=notifier,
        )

        self._rebootUseCase = RebootUseCase(
            orchestrator=RebootOrchestrator(nodes=nodes, nodeAgent=nodeAgent),
            notifier=notifier,
        )

        self._useCaseLogFileList = FileListUseCase(
            fileSystem=fileSystemAdapter,
            notifier=notifier
        )

        self._useCaseLogFileUpload = FileUploadUseCase(
            fileSystem=fileSystemAdapter,
            uploadAdapter=fileTransferClient,
            notifyHandler=notifier.notifyLogFileUpload
        )

        self._useCaseConfigFileUpload = FileUploadUseCase(
            fileSystem=fileSystemAdapter,
            uploadAdapter=fileTransferClient,
            notifyHandler=notifier.notifyConfigFileUpload
        )

        self._useCaseConfigFileUpgrade = FileUpgradeUseCase(
            fileSystem=fileSystemAdapter,
            downloadAdapter=fileTransferClient,
            notifier=notifier
        )

        self._useCaseSystemInfo = SystemInfoUseCase(notifier=notifier)

    async def dispatch(self, payload: bytes) -> None:
        """Parse RPC payload and dispatch to the registered method handler."""
        try:
            data = json.loads(payload.decode("utf-8"))
        except json.JSONDecodeError:
            logger.error("Invalid JSON RPC payload: %r", payload)
            return

        schemaPath = Path(__file__).parent.parent / "schemas/v1_0/0_RPC.schema.json"
        if not jsonSchemaValidationPath(data, schemaPath):
            logger.error("JSON RPC payload could not be validated by schema: %r", payload)
            return

        method = data.get("method")
        params = data.get("params", {})

        handler = _rpcRoutes.get(method)
        if not handler:
            logger.warning("No RPC handler for method '%s'", method)
            return

        await handler(self, params)

    @rpc("BootDataNotification")
    async def handleBootDataNotification(self, params: dict) -> None:
        """
        A.1.2
        """
        logger.info("Received BootDataNotification request: %s", params)

        await self._systemInfoUseCase.execute()


    @rpc("upgradeFw")
    async def handleUpgradeFw(self, params: dict) -> None:
        required = ["taskId", "url", "firmware"]
        missing = [k for k in required if k not in params]
        if missing:
            logger.error("upgradeFw missing params: %s", missing)
            return

        dto = SoftwareUpgradeDto(
            targetVersion=str(params["firmware"]),
            downloadUrl=str(params["url"]),
            taskId=str(params["taskId"]),
            selector=NodeSelector.all(),
        )

        logger.info("Received upgradeFw command: %s", params)
        await self._softwareUpgradeUseCase.execute(dto)

    @rpc("uploadLogFileList")
    async def handleUploadLogFileList(self, params: dict) -> None:
        """
        A.3.2 (Get filelist): CSP requests log file list.
        Spec snippet typically has method=uploadLogFileList and may include params (optional).
        We'll just log it for now so routing works, and you can plug a usecase later.
        """
        logger.info("Received uploadLogFileList command: %s", params)
        pathToFolder = Path(self.ECP_WORKING_DIR + "/log")

        fileListDto = FileListDto(path=pathToFolder, taskId=params["taskId"], datatypes=[".log"])
        await self._useCaseLogFileList.execute(fileListDto),

    @rpc("uploadUrl")
    async def handleUploadLogFile(self, params: dict) -> None:
        """
        A.3.1
        """
        logger.info("Received uploadUrl command: %s", params)

        fileName = params.get("filename", None)
        filePath = Path(self.ECP_WORKING_DIR + "/log")

        if isinstance(fileName, str):
            filePath = filePath / fileName
        else:
            fileName = "logs.zip"

        fileUploadDto = FileUploadDto(path=filePath, uploadUrl=params["url"] + f"/{fileName}", taskId=params["taskId"])
        await self._useCaseLogFileUpload.execute(fileUploadDto)

    @rpc("upgradeConfig")
    async def handleUpgradeConfigFile(self, params: dict) -> None:
        """
        A.5.1
        """
        logger.info("Received upgradeConfig command: %s", params)

        pathToConfigFile = Path(os.environ.get("UPLOAD_DIR", "/mnt/persistent-data/uploads") + "/config.json")

        fileUpgradDto = FileUpgradeDto(taskId=params["taskId"], downloadUrl=params["url"], filename=params["filename"], checksum=params["checksum"], checksumType=params["checksumType"], expireDate=params["urlExpired"], version=params["version"], destinationPath=pathToConfigFile)

        success = await self._useCaseConfigFileUpgrade.execute(fileUpgradDto)

        if not success:
            return

        # hard reboot afer sucessful config download and check
        dto = RebootDto(
            taskId=str(params["taskId"]),
            type=RebootType.hard,
            entity=RebootEntity.os,
            selector=NodeSelector.one("baseboard"),
        )
        await self._rebootUseCase.execute(dto)

    @rpc("uploadConfig")
    async def handleUploadConfigFile(self, params: dict) -> None:
        """
        A.5.2
        """
        logger.info("Received uploadConfig command: %s", params)

        filePath = Path(self.ECP_WORKING_DIR + "/config/config.json")

        fileUploadDto = FileUploadDto(path=filePath, uploadUrl=params["url"] + "/config.json",
                                      taskId=params["taskId"])
        await self._useCaseLogFileUpload.execute(fileUploadDto)

    @rpc("reboot")
    async def handleReboot(self, params: dict) -> None:
        rebootType = RebootType(str(params["type"]))

        if rebootType == RebootType.soft:
            entity = RebootEntity.ecp
        elif rebootType == RebootType.hard:
            entity = RebootEntity.os
        else:
            entity = RebootEntity.modem

        dto = RebootDto(
            taskId=str(params["taskId"]),
            type=rebootType,
            entity=entity,
            selector=NodeSelector.all(),
        )

        logger.info("Received reboot command: %s", params)
        await self._rebootUseCase.execute(dto)

