from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers

from dcim.choices import *
from dcim.models import Cable, CableBundle, CablePath, CableTermination
from netbox.api.fields import ChoiceField, ContentTypeField
from netbox.api.gfk_fields import GFKSerializerField
from netbox.api.serializers import (
    BaseModelSerializer,
    GenericObjectSerializer,
    NetBoxModelSerializer,
    PrimaryModelSerializer,
)
from tenancy.api.serializers_.tenants import TenantSerializer
from utilities.api import get_serializer_for_model

__all__ = (
    'CableBundleSerializer',
    'CablePathSerializer',
    'CableSerializer',
    'CableTerminationSerializer',
    'CabledObjectSerializer',
    'TracedCableSerializer',
)


class CableBundleSerializer(PrimaryModelSerializer):
    cable_count = serializers.IntegerField(read_only=True, default=0)

    class Meta:
        model = CableBundle
        fields = [
            'id', 'url', 'display_url', 'display', 'name', 'description', 'owner', 'comments', 'tags',
            'custom_fields', 'created', 'last_updated', 'cable_count',
        ]
        brief_fields = ('id', 'url', 'display', 'name', 'description')


class CableSerializer(PrimaryModelSerializer):
    a_terminations = GenericObjectSerializer(many=True, required=False)
    b_terminations = GenericObjectSerializer(many=True, required=False)
    status = ChoiceField(choices=LinkStatusChoices, required=False)
    profile = ChoiceField(choices=CableProfileChoices, required=False)
    tenant = TenantSerializer(nested=True, required=False, allow_null=True)
    length_unit = ChoiceField(choices=CableLengthUnitChoices, allow_blank=True, required=False, allow_null=True)
    bundle = CableBundleSerializer(nested=True, required=False, allow_null=True, default=None)

    class Meta:
        model = Cable
        fields = [
            'id', 'url', 'display_url', 'display', 'type', 'a_terminations', 'b_terminations', 'status', 'profile',
            'tenant', 'bundle', 'label', 'color', 'length', 'length_unit', 'description', 'owner', 'comments', 'tags',
            'custom_fields', 'created', 'last_updated',
        ]
        brief_fields = ('id', 'url', 'display', 'label', 'description')


class TracedCableSerializer(BaseModelSerializer):
    """
    Used only while tracing a cable path.
    """

    class Meta:
        model = Cable
        fields = [
            'id', 'url', 'display_url', 'type', 'status', 'label', 'color', 'length', 'length_unit', 'description',
        ]


class CableTerminationSerializer(NetBoxModelSerializer):
    termination_type = ContentTypeField(
        read_only=True,
    )
    termination = GFKSerializerField(read_only=True)

    class Meta:
        model = CableTermination
        fields = [
            'id', 'url', 'display', 'cable', 'cable_end', 'termination_type', 'termination_id',
            'termination', 'connector', 'positions', 'created', 'last_updated',
        ]
        read_only_fields = fields
        brief_fields = (
            'id', 'url', 'display', 'cable', 'cable_end', 'connector', 'positions', 'termination_type',
            'termination_id',
        )


class CablePathSerializer(serializers.ModelSerializer):
    path = serializers.SerializerMethodField(read_only=True)

    class Meta:
        model = CablePath
        fields = ['id', 'path', 'is_active', 'is_complete', 'is_split']

    @extend_schema_field(serializers.ListField)
    def get_path(self, obj):
        ret = []
        for nodes in obj.path_objects:
            if not nodes:
                # The path contains an invalid object
                return []
            serializer = get_serializer_for_model(nodes[0])
            context = {'request': self.context['request']}
            ret.append(serializer(nodes, nested=True, many=True, context=context).data)
        return ret


class CabledObjectSerializer(serializers.ModelSerializer):
    cable = CableSerializer(nested=True, read_only=True, allow_null=True)
    # Use DRF's ChoiceField; NetBox's ChoiceField would return a value/label object.
    cable_end = serializers.ChoiceField(choices=CableEndChoices, read_only=True, allow_null=True)
    link_peers_type = serializers.SerializerMethodField(read_only=True, allow_null=True)
    link_peers = serializers.SerializerMethodField(read_only=True)
    _occupied = serializers.SerializerMethodField(read_only=True)

    @extend_schema_field(OpenApiTypes.STR)
    def get_link_peers_type(self, obj):
        """
        Return the type of the peer link terminations, or None.
        """
        if not obj.cable:
            return None

        if obj.link_peers:
            return f'{obj.link_peers[0]._meta.app_label}.{obj.link_peers[0]._meta.model_name}'

        return None

    @extend_schema_field(serializers.ListField)
    def get_link_peers(self, obj):
        """
        Return the appropriate serializer for the link termination model.
        """
        if not obj.link_peers:
            return []

        # Return serialized peer termination objects
        serializer = get_serializer_for_model(obj.link_peers[0])
        context = {'request': self.context['request']}
        return serializer(obj.link_peers, nested=True, many=True, context=context).data

    @extend_schema_field(serializers.BooleanField)
    def get__occupied(self, obj):
        return obj._occupied
