import json
from typing import Union

from gumband_python_sdk.service_name import ServiceName
from gumband_python_sdk.network_handler import NetworkHandler
from gumband_python_sdk.constants import SocketEventType
from gumband_python_sdk.logging_handler import get_local_logger

SERVICE = ServiceName.EXHIBIT

local_logger = get_local_logger()

"""
ManifestService
======================
This class is responsible for managing and synchronizing the manifest with Gumband.
It also allows the retrieval and modification of the settings and statuses within the manifest.
This class is intended as a private class. Please use the Gumband service instead.
"""


class ManifestService:
    def __init__(self, exhibit_id: int, network_handler: NetworkHandler, manifest=None):
        """
        Initializes the ManifestService.

        Args:
            exhibit_id (int): The ID of the exhibit.
            network_handler (NetworkHandler): An instance of the network handler.
            manifest (dict, optional): The manifest, which will be populated upon connection to Gumband.
        """
        self.exhibit_id = exhibit_id
        self.network_handler = network_handler
        self.manifest = manifest or {}
        local_logger.info(f"ManifestService initialized for exhibit {exhibit_id}.")

    def parse_manifest(self, manifest_input: Union[str, dict]):
        """
        Parses the manifest and sets the metrics configuration.

        Args:
            manifest_input (Union[str, dict]): The manifest data, provided as a JSON string, dictionary, or file path.

        Raises:
            ValueError: If the input is invalid or the manifest validation fails.
        """
        if isinstance(manifest_input, dict):
            local_logger.debug("Manifest is a dictionary object.")
            self.manifest = manifest_input
        elif isinstance(manifest_input, str):
            try:
                self.manifest = json.loads(manifest_input)
                local_logger.debug("Manifest is a JSON string.")
            except json.JSONDecodeError:
                self._read_manifest_file(manifest_input)
                local_logger.debug(f"Manifest loaded from file: {manifest_input}")
        else:
            local_logger.error("Invalid manifest input type.")
            raise ValueError("Manifest must be a dict, JSON string, or file path.")

        # Load manifest into the manifest service
        self._load_manifest_from_object(self.manifest)
        # Optionally validate the manifest
        if not self._validate_manifest():
            local_logger.error("Manifest validation failed.")
            raise ValueError("Manifest validation failed.")
        else:
            local_logger.debug("Manifest validated successfully.")

    async def setSdkVersion(self):
        """
        Reports the SDK version to the Gumband server.

        Returns:
            Response: The response from the server.

        Raises:
            Exception: If the server communication fails.
        """
        try:
            endpoint = f"/exhibit/{self.exhibit_id}/sdk-version"
            data = {"sdkVersion": "0.0.0"}
            response = await self.network_handler.post(service=SERVICE, endpoint=endpoint, data=data)
            local_logger.info("SDK version set")
            return response
        except Exception as e:
            local_logger.error(f"Failed to set SDK version: {e}")
            self.emit(SocketEventType.ERROR.value, f"Failed to set SDK version: {e}")

    async def setManifest(self):
        """
        Uploads the manifest to the server and returns a fully populated manifest.

        Returns:
            dict: The manifest retrieved from the server.

        Raises:
            Exception: If the upload or subsequent retrieval of the manifest fails.
        """
        try:
            await self._sync_manifest_with_server()
            return await self.get_manifest()
        except Exception as e:
            local_logger.error(f"Failed to upload manifest: {e}")
            raise

    async def get_manifest(self) -> dict:
        """
        Retrieves the manifest from the server.

        Returns:
            dict: The manifest retrieved from the server.

        Raises:
            Exception: If the retrieval fails.
        """
        try:
            endpoint = f"/exhibit/{self.exhibit_id}/manifest"
            manifest = await self.network_handler.get(service=SERVICE, endpoint=endpoint)
            self.manifest = manifest.get("manifest", {})
            return self.manifest
        except Exception:
            raise

    async def set_status(self, status_id, status_value) -> dict:
        """
        Sets a specific status value for the component.

        Args:
            status_id (str): The ID of the status to set.
            status_value (Any): The value to set for the status.

        Returns:
            dict: The server's response.

        Raises:
            Exception: If the status update fails.
        """
        try:
            endpoint = f"/exhibit/{self.exhibit_id}/status/{status_id}"
            data = {"value": status_value}
            return await self.network_handler.post(service=SERVICE, endpoint=endpoint, data=data)
        except Exception:
            raise

    async def get_status(self, status_id) -> dict:
        """
        Retrieves a specific status value for the component.

        Args:
            status_id (str): The ID of the status to retrieve.

        Returns:
            dict: The retrieved status value.

        Raises:
            Exception: If the status retrieval fails.
        """
        try:
            endpoint = f"/exhibit/{self.exhibit_id}/status/{status_id}"
            response = await self.network_handler.get(service=SERVICE, endpoint=endpoint)
            return response["status"]
        except Exception:
            raise

    async def set_setting(self, setting_id, setting_value) -> dict:
        """
        Sets a specific setting value for the component.

        Args:
            setting_id (str): The ID of the setting to set.
            setting_value (Any): The value to set for the setting.

        Returns:
            dict: The updated setting.

        Raises:
            Exception: If the setting update fails.
        """
        try:
            endpoint = f"/exhibit/{self.exhibit_id}/setting/{setting_id}"
            data = {"value": setting_value}
            response = await self.network_handler.post(service=SERVICE, endpoint=endpoint, data=data)
            return response["setting"]
        except Exception:
            raise

    async def get_setting(self, setting_id) -> dict:
        """
        Retrieves a specific setting value for the component.

        Args:
            setting_id (str): The ID of the setting to retrieve.

        Returns:
            dict: The retrieved setting value.

        Raises:
            Exception: If the setting retrieval fails.
        """
        try:
            endpoint = f"/exhibit/{self.exhibit_id}/setting/{setting_id}"
            response = await self.network_handler.get(service=SERVICE, endpoint=endpoint)
            return response["setting"]
        except Exception:
            raise

    def _read_manifest_file(self, manifest_file: str):
        """
        Private function
        Reads the manifest from a file.

        Args:
            manifest_file (str): The file path to the manifest JSON.

        Raises:
            FileNotFoundError: If the file does not exist.
            json.JSONDecodeError: If the file content is not valid JSON.
        """
        try:
            with open(manifest_file, "r") as file:
                self.manifest = json.load(file)
                local_logger.debug(f"Manifest loaded from file: {manifest_file}")
        except FileNotFoundError:
            local_logger.error(f"Manifest file not found: {manifest_file}")
            raise
        except json.JSONDecodeError:
            local_logger.error(f"Invalid JSON in manifest file: {manifest_file}")
            raise

    def _load_manifest_from_object(self, manifest_object: dict):
        """
        Private function
        Loads a manifest from a dictionary object.

        Args:
            manifest_object (dict): The manifest dictionary.
        """
        self.manifest = manifest_object
        local_logger.debug("Manifest loaded from object.")

    def _validate_manifest(self) -> bool:
        """
        Private function
        Validates the manifest structure.

        Returns:
            bool: `True` if the manifest is valid, otherwise `False`.
        """
        # Add validation logic here based on your manifest schema
        if "manifest" not in self.manifest:
            local_logger.error("Manifest is missing 'manifest' key.")
            return False
        # Further validation as needed
        local_logger.info("Manifest validation successful.")
        return True

    async def _sync_manifest_with_server(self):
        """
        Private function
        Synchronizes the manifest with the server.

        Returns:
            Response: The response from the server.

        Raises:
            Exception: If the synchronization fails.
        """
        try:
            endpoint = f"/exhibit/{self.exhibit_id}/manifest"
            manifest = self.manifest.get("manifest", {})
            data = {"exhibitId": self.exhibit_id, "manifest": manifest}
            response = await self.network_handler.post(service=SERVICE, endpoint=endpoint, data=data)
            return response
        except Exception as e:
            local_logger.error(f"Failed to synchronize manifest: {e}")
            raise
