# Copyright (C) 2021-2022 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.

"""
Bindings for the Here Real Time Traffic Incidents.
"""
from dataclasses import dataclass
from enum import Enum
from typing import Any, List, Mapping, Optional

from google.protobuf.message import Message
from here.content.base import ContentIndexer, ContentPartition, SingleLayerBinding
from here.content.real_time_traffic.traffic_attributes import (
    TrafficAttributes,
    parse_topology_segment_refs,
    parse_topology_segments,
)
from here.content.utils.proto import decode_message, has_field, has_field_attr
from here.platform.adapter import DecodedMessage, Identifier, Partition


class Criticality(Enum):
    """Represent criticality of an incident."""

    CRITICAL = 0
    MAJOR = 1
    MINOR = 2
    LOW_IMPACT = 3


@dataclass
class Incident:
    """Traffic Incidents."""

    item: TrafficAttributes
    primary_incident: Optional[DecodedMessage]
    criticality: Optional[str]
    criticality_level: Optional[int]
    scheduled_start_timestamp: Optional[int]
    scheduled_end_timestamp: Optional[int]
    comment: Optional[str]
    source: Optional[str]
    children: Optional[List[DecodedMessage]]
    tec: Optional[DecodedMessage]

    indexer = ContentIndexer(
        partition=lambda a: a.item.partition,  # type: ignore
        identifier=lambda a: a.item.id,  # type: ignore
        refs={
            "topology_segment": lambda a: [
                s for s in a.item.topology_segment_refs  # type: ignore
            ],
        },
    )


class TrafficIncidents(SingleLayerBinding):
    """
    Bindings for the Here Real Time Traffic Incidents.
    """

    @classmethod
    def schema_hrn(cls) -> List[str]:
        """
        :return: the HRN of schema this binding can parse and index
        """
        return ["hrn:here:schema:::com.here.traffic.realtime:traffic_v2:2.1.15"]

    @classmethod
    def object_types(cls) -> Mapping[str, type]:
        """
        :return: the types of attributes supported by this binding
        """
        return {"incidents": Incident}

    @classmethod
    def parse_and_index(cls, partition_id: str, msg: Message) -> ContentPartition:
        """
        Parse and index decoded message.

        :param partition_id: identifier of the partition
        :param msg: decoded content from the layer, in the form of a Protobuf message
        :return: the content of a partition parsed and indexed in its own data structure
        """
        assert hasattr(msg, "items")

        def parse_decoded_optional(msg: Message, field: str) -> Optional[DecodedMessage]:
            if has_field(msg, field):
                return decode_message(msg.__getattribute__(field))
            elif hasattr(msg, field):
                return decode_message(getattr(msg, field))
            else:
                return None

        def get_optional(obj: Message, field: str) -> Any:
            if has_field(obj, field):
                return obj.__getattribute__(field).value
            elif hasattr(obj, field):
                return getattr(obj, field)
            else:
                return None

        incidents = [
            Incident(
                item=TrafficAttributes(
                    id=Identifier(i.id),
                    partition=Partition(i.partition)
                    if has_field(i, "partition")
                    else Partition(partition_id),
                    original_id=Identifier(i.original_id)
                    if has_field_attr(i, "original_id")
                    else None,
                    created_timestamp=get_optional(i, "created_timestamp"),
                    updated_timestamp=get_optional(i, "updated_timestamp"),
                    expiry_timestamp=get_optional(i, "expiry_timestamp"),
                    processing_timestamp=get_optional(i, "processing_timestamp"),
                    map_version=get_optional(i, "map_version"),
                    is_active=i.is_active,
                    topology_segment=parse_topology_segments(i),
                    supplementary_location_ref=decode_message(i.supplementary_location_ref)
                    if has_field(i, "supplementary_location_ref")
                    else None,
                    topology_segment_refs=parse_topology_segment_refs(i, partition_id),
                ),
                primary_incident=decode_message(i.incident.primary_incident)
                if has_field(i.incident, "primary_incident")
                else None,
                criticality=Criticality(i.incident.criticality).name,
                criticality_level=i.incident.criticality,
                scheduled_start_timestamp=get_optional(i.incident, "scheduled_start_timestamp"),
                scheduled_end_timestamp=get_optional(i.incident, "scheduled_end_timestamp"),
                comment=get_optional(i.incident, "comment"),
                source=get_optional(i.incident, "source"),
                children=[
                    decode_message(c)
                    for c in i.incident.children
                    if has_field(i.incident, "children")
                ],
                tec=parse_decoded_optional(i.incident, "tec"),
            )
            # Discuss the behaviour when the topology_segment is absent in the incident item
            # and adapt the condition below
            for i in msg.items  # type: ignore
            if has_field(i, "topology_segment") or has_field(i, "supplementary_location_ref")
        ]

        return ContentPartition(cls, incidents=incidents)
