from rest_framework import serializers
from authentication.models import KnownIdentity, ToolshedUser, FriendRequestIncoming, Group, GroupInviteIncoming
from authentication.serializers import OwnerSerializer, GroupOwnerSerializer
from files.models import File
from files.serializers import FileSerializer
from toolshed.models import Category, Property, ItemProperty, InventoryItem, Tag, StorageLocation, WorkflowInstance


def parse_handle(handle):
    """Parse a fully qualified handle like 'git:base#property:length' into (origin, entity_type, name)"""
    if '#' not in handle:
        # Fallback to old format (just name)
        return None, None, handle
    origin, rest = handle.split('#', 1)
    if ':' not in rest:
        return origin, None, rest
    entity_type, name = rest.split(':', 1)
    return origin, entity_type, name


def resolve_category_handle(handle):
    """Resolve a fully qualified handle to a Category object"""
    origin, entity_type, name = parse_handle(handle)
    if origin and entity_type == 'category':
        return Category.objects.get(origin=origin, name=name)
    # Fallback to name-only lookup
    return Category.objects.get(name=handle.split('/')[-1])


def resolve_property_handle(handle):
    """Resolve a fully qualified handle to a Property object"""
    origin, entity_type, name = parse_handle(handle)
    if origin and entity_type == 'property':
        return Property.objects.get(origin=origin, name=name)
    # Fallback to name-only lookup
    return Property.objects.get(name=handle)


def resolve_tag_handle(handle):
    """Resolve a fully qualified handle to a Tag object"""
    origin, entity_type, name = parse_handle(handle)
    if origin and entity_type == 'tag':
        return Tag.objects.get(origin=origin, name=name)
    # Fallback to name-only lookup
    return Tag.objects.get(name=handle)



class FriendSerializer(serializers.ModelSerializer):
    username = serializers.SerializerMethodField()

    class Meta:
        model = KnownIdentity
        fields = ['id', 'username', 'public_key']

    def get_username(self, obj):
        return obj.username + '@' + obj.domain


class FriendRequestSerializer(serializers.ModelSerializer):
    befriender = serializers.SerializerMethodField()

    class Meta:
        model = FriendRequestIncoming
        fields = ['befriender', 'befriender_public_key', 'secret', 'id']

    def get_befriender(self, obj):
        return obj.befriender_username + '@' + obj.befriender_domain


class GroupMemberSerializer(serializers.ModelSerializer):
    username = serializers.SerializerMethodField()

    class Meta:
        model = KnownIdentity
        fields = ['id', 'username', 'public_key']

    def get_username(self, obj):
        return obj.username + '@' + obj.domain


class GroupSerializer(serializers.ModelSerializer):
    handle = serializers.SerializerMethodField()
    members = GroupMemberSerializer(many=True, read_only=True)

    class Meta:
        model = Group
        fields = ['id', 'name', 'domain', 'handle', 'members']
        read_only_fields = ['domain', 'handle', 'members']

    def get_handle(self, obj):
        return str(obj)


class GroupInviteIncomingSerializer(serializers.ModelSerializer):
    group = serializers.SerializerMethodField()
    inviter = serializers.SerializerMethodField()

    class Meta:
        model = GroupInviteIncoming
        fields = ['id', 'group', 'inviter', 'secret']

    def get_group(self, obj):
        return f"#{obj.group_name}@{obj.group_domain}"

    def get_inviter(self, obj):
        return obj.inviter_username + '@' + obj.inviter_domain


class PropertySerializer(serializers.ModelSerializer):
    category = serializers.SerializerMethodField()
    handle = serializers.SerializerMethodField()

    def get_category(self, obj):
        return resolve_category_handle(obj.category.get_handle()) if obj.category else None

    def get_handle(self, obj):
        return obj.get_handle()

    class Meta:
        model = Property
        fields = ['name', 'description', 'category', 'unit_symbol', 'unit_name', 'unit_name_plural', 'base2_prefix', 'handle']


class CategorySerializer(serializers.ModelSerializer):
    handle = serializers.SerializerMethodField()

    class Meta:
        model = Category
        fields = ['name', 'handle']

    def get_handle(self, obj):
        return obj.get_handle()

    def to_representation(self, instance):
        return instance.name

    def to_internal_value(self, data):
        return resolve_category_handle(data.split("/")[-1])


class StorageLocationSerializer(serializers.ModelSerializer):
    owner = OwnerSerializer(read_only=True)
    category = serializers.CharField(required=False, allow_null=True, allow_blank=True)
    path = serializers.SerializerMethodField()

    class Meta:
        model = StorageLocation
        fields = ['id', 'name', 'description', 'path', 'category', 'owner', 'parent']
        read_only_fields = ['path']

    @staticmethod
    def get_path(obj):
        if obj.parent:
            return StorageLocationSerializer.get_path(obj.parent) + "/" + obj.name
        return obj.name


class ItemPropertySerializer(serializers.ModelSerializer):
    property = PropertySerializer(read_only=True)
    handle = serializers.SerializerMethodField()

    class Meta:
        model = ItemProperty
        fields = ['property', 'value', 'handle']

    def get_handle(self, obj):
        return obj.property.get_handle()

    def to_representation(self, instance):
        return {'value': instance.value, 'name': instance.property.name}

    def to_internal_value(self, data):
        prop = resolve_property_handle(data.get('name') or data.get('handle'))
        value = data['value']
        return {'property': prop, 'value': value}


class InventoryItemSerializer(serializers.ModelSerializer):
    owner = OwnerSerializer(read_only=True)
    owner_group = GroupOwnerSerializer(read_only=True)
    tags = serializers.SerializerMethodField()
    tags_input = serializers.ListField(child=serializers.CharField(), write_only=True, required=False)
    properties = ItemPropertySerializer(many=True, required=False, source='itemproperty_set')
    category = CategorySerializer(required=False, allow_null=True)
    files = FileSerializer(many=True, read_only=True)

    class Meta:
        model = InventoryItem
        fields = ['id', 'name', 'description', 'owner', 'owner_group', 'category', 'availability_policy',
                  'owned_quantity', 'tags', 'tags_input', 'properties', 'files', 'storage_location']

    def get_tags(self, obj):
        return [tag.name for tag in obj.tags.all()]

    def to_internal_value(self, data):
        files = data.pop('files', [])
        tags_input = data.pop('tags_input', data.pop('tags', []))
        ret = super().to_internal_value(data)
        ret['tags'] = [resolve_tag_handle(tag) for tag in tags_input]
        ret['files'] = files
        return ret

    def create(self, validated_data):
        tags = validated_data.pop('tags', [])
        props = validated_data.pop('itemproperty_set', [])
        files = validated_data.pop('files', [])
        item = InventoryItem.objects.create(**validated_data)
        for tag in tags:
            item.tags.add(tag, through_defaults={})
        for prop in props:
            ItemProperty.objects.create(inventory_item=item, property=prop['property'], value=prop['value'])
        for file in files:
            if type(file) == dict:
                file_serializer = FileSerializer(data=file)
                if file_serializer.is_valid():
                    file_serializer.save()
                    item.files.add(file_serializer.instance)
                else:
                    raise serializers.ValidationError(file_serializer.errors)
            elif type(file) == int:
                if File.objects.filter(id=file).exists():
                    item.files.add(File.objects.get(id=file))
                else:
                    raise serializers.ValidationError("File with id {} does not exist".format(file))
        item.save()
        return item

    def update(self, instance, validated_data):
        tags = validated_data.pop('tags', [])
        props = validated_data.pop('itemproperty_set', [])
        item = super().update(instance, validated_data)
        item.tags.clear()
        item.properties.clear()
        if 'category' not in validated_data:
            item.category = None
        for tag in tags:
            item.tags.add(tag)
        for prop in props:
            ItemProperty.objects.create(inventory_item=item, property=prop['property'], value=prop['value'])
        item.save()
        return item

class WorkflowInstanceSerializer(serializers.ModelSerializer):
    owner = serializers.StringRelatedField(read_only=True)
    # Hash is enough to identify a staged file (the client computes the same SHA-256 the backend
    # does, and can fetch bytes from a hash-derived storage path) - for anything staged by *this*
    # session there's nothing more to say, and for a file staged elsewhere (another device/tab),
    # hash is what lets this session recognize and fetch it. Unlike InventoryItemSerializer.files,
    # no fuller FileSerializer representation is needed here.
    staged_files = serializers.SerializerMethodField()

    class Meta:
        model = WorkflowInstance
        fields = ['id', 'slug', 'state', 'payload', 'owner', 'staged_files', 'created_at', 'updated_at']
        read_only_fields = ['owner', 'staged_files', 'created_at', 'updated_at']

    def get_staged_files(self, obj):
        return list(obj.staged_files.values_list('hash', flat=True))

