# Copyright (C) 2020-2023 HERE Global B.V. and its affiliate(s).
# All rights reserved.
#
# This software and other materials contain proprietary information
# controlled by HERE and are protected by applicable copyright legislation.
# Any use and utilization of this software and other materials and
# disclosure to any third parties is conditional upon having a separate
# agreement with HERE for the access, use, utilization or disclosure of this
# software. In the absence of such agreement, the use of the software is not
# allowed.

"""
HERE Platform Json Parser module
"""

import io
import json
import os
import zipfile
from pathlib import Path
from typing import Union

import jsonschema
from google.protobuf.message import Message
from here.platform.exceptions import SchemaException
from here.platform.schema.parser import Parser


class JsonParser(Parser):
    """A class for parsing Json data of a catalog layer partition."""

    _SCHEMA_ARTIFACTS_PATH = os.path.abspath(os.path.dirname(__file__))

    def __init__(self, schema_pkg_file: Union[str, Path, io.BytesIO]):
        """
        Initialize a JsonParser instance.

        :param schema_pkg_file: the schema package zip-file as a str, Path or file-like object
        :raises SchemaException: if the required meta information is missing in schema package
        """
        layer_manifest, _ = Parser.extract_layer_info(schema_pkg_file)
        if not layer_manifest:
            raise SchemaException(
                f"Invalid schema. Layer manifest file {Parser.LAYER_MANIFEST} not found in schema "
                f"package file"
            )

        self.main_partition_message = layer_manifest["main"]["message"]
        self.main_partition_message_source = layer_manifest["main"]["source"]

        if not self.main_partition_message:
            raise SchemaException(
                f"Unable to determine the main message defined in the "
                f"{Parser.LAYER_MANIFEST} for schema artifact"
            )

        if not self.main_partition_message_source:
            raise SchemaException(
                f"Unable to determine the main message source defined in the "
                f"{Parser.LAYER_MANIFEST} for schema artifact"
            )

        self._schema: dict = {}
        self._ref_resolver = None
        self._schema_pgk = schema_pkg_file
        self.extract_json_schema()

    def extract_json_schema(self) -> dict:
        """
        Extract layer info from an input zipfile, locate the main message source
        and initialize the json-schema object.

        :returns: json-schema object
        """
        if not self._ref_resolver:
            abs_path = os.path.join(
                self._SCHEMA_ARTIFACTS_PATH, self.main_partition_message_source
            )
            self._ref_resolver = jsonschema.RefResolver(
                "file://%s/" % os.path.dirname(abs_path), None
            )
        if not self._schema:
            input_zipfile = zipfile.ZipFile(self._schema_pgk)
            input_zipfile.extractall(path=self._SCHEMA_ARTIFACTS_PATH)
            main_message_path = os.path.join(
                self._SCHEMA_ARTIFACTS_PATH, self.main_partition_message_source
            )
            with open(main_message_path, "r") as schema:
                mainSchema = json.load(schema)
                if self.main_partition_message:
                    message_path = self.main_partition_message.split("/")
                    for i in range(0, len(message_path)):
                        if message_path[i] == "#":
                            continue
                        mainSchema = mainSchema[message_path[i]]
                    self._schema = mainSchema
        return self._schema

    def validate_json_schema(self, data: dict):
        """
        Validates the given json instance against the defined schema.

        :param data: json instance to be validated
        """
        jsonschema.validate(instance=data, schema=self._schema, resolver=self._ref_resolver)

    def parse_message(self, message: bytes) -> Union[Message, dict]:
        """
        Parse a message and returns the corresponding partition class instance.

        :param message: message received as blob
        :return: parsed message
        """
        decoded = json.loads(message)
        assert isinstance(decoded, dict)
        self.validate_json_schema(decoded)
        return decoded

    def serialize_bytes(self, data: Union[Message, dict]) -> bytes:
        """
        Parse a Json message and returns the bytes.

        :param data: Json message
        :return: bytes
        """
        assert isinstance(data, dict)
        self.validate_json_schema(data)
        message_bytes = json.dumps(data).encode("utf-8")
        assert isinstance(message_bytes, bytes)
        return message_bytes
