"""DRF serializers for platform_core generic services (Attachment, Comment,
Activity, CustomField).
"""

from __future__ import annotations

from rest_framework import serializers

from simorgh.apps.platform_core.models import (
    Activity,
    Attachment,
    Comment,
    CustomFieldDefinition,
    CustomFieldType,
    CustomFieldValue,
    ExportJob,
    ExportFormat,
    ExportJobStatus,
    FormDefinition,
    FormSubmission,
    ImportJob,
    ImportJobStatus,
    MessageChannel,
    MessageTemplate,
    Note,
    NoteVisibility,
    PageOrientation,
    PaperSize,
    PrintTemplate,
    Tag,
    TagAssignment,
)


# ---------------------------------------------------------------------------
# Attachment
# ---------------------------------------------------------------------------


class AttachmentSerializer(serializers.ModelSerializer):
    filename = serializers.CharField(source="file.filename", read_only=True)
    content_type = serializers.CharField(source="file.content_type", read_only=True)
    size_bytes = serializers.IntegerField(source="file.size_bytes", read_only=True)
    url = serializers.CharField(source="file.path", read_only=True)
    uploaded_by_id = serializers.IntegerField(read_only=True)

    class Meta:
        model = Attachment
        fields = (
            "id",
            "filename",
            "content_type",
            "size_bytes",
            "kind",
            "description",
            "sort_order",
            "uploaded_by_id",
            "url",
            "created_at",
        )
        read_only_fields = fields


class AttachmentUploadSerializer(serializers.Serializer):
    """Input schema for ``POST .../attachments/``."""

    file = serializers.FileField()
    description = serializers.CharField(required=False, default="", allow_blank=True)
    kind = serializers.ChoiceField(
        choices=Attachment._meta.get_field("kind").choices,
        required=False,
        default="document",
    )


# ---------------------------------------------------------------------------
# Comment
# ---------------------------------------------------------------------------


class CommentAuthorSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.SerializerMethodField()

    def get_name(self, obj) -> str:
        return str(obj) if obj else ""


class ReplySerializer(serializers.ModelSerializer):
    author = serializers.SerializerMethodField()

    class Meta:
        model = Comment
        fields = (
            "id",
            "body",
            "author",
            "edited_at",
            "created_at",
            "updated_at",
        )

    def get_author(self, obj):
        if obj.author:
            return {"id": obj.author_id, "name": str(obj.author)}
        return None


class CommentSerializer(serializers.ModelSerializer):
    author = serializers.SerializerMethodField()
    replies = ReplySerializer(many=True, read_only=True)

    class Meta:
        model = Comment
        fields = (
            "id",
            "body",
            "author",
            "parent_id",
            "replies",
            "edited_at",
            "created_at",
            "updated_at",
        )

    def get_author(self, obj):
        if obj.author:
            return {"id": obj.author_id, "name": str(obj.author)}
        return None


class CommentCreateSerializer(serializers.Serializer):
    body = serializers.CharField(min_length=1)
    parent_id = serializers.UUIDField(required=False, allow_null=True, default=None)


class CommentEditSerializer(serializers.Serializer):
    body = serializers.CharField(min_length=1)


# ---------------------------------------------------------------------------
# Activity
# ---------------------------------------------------------------------------


class ActivitySerializer(serializers.ModelSerializer):
    actor = serializers.SerializerMethodField()

    class Meta:
        model = Activity
        fields = (
            "id",
            "verb",
            "actor",
            "occurred_at",
            "extra",
        )

    def get_actor(self, obj):
        if obj.actor:
            return {"id": obj.actor_id, "name": str(obj.actor)}
        return None


# ---------------------------------------------------------------------------
# Custom Fields
# ---------------------------------------------------------------------------


class CustomFieldDefinitionSerializer(serializers.ModelSerializer):
    class Meta:
        model = CustomFieldDefinition
        fields = (
            "id",
            "entity_type",
            "key",
            "label_key",
            "field_type",
            "is_required",
            "sort_order",
            "validation",
            "options",
            "help_text",
        )
        read_only_fields = ("id", "entity_type")


class CustomFieldDefinitionCreateSerializer(serializers.Serializer):
    key = serializers.SlugField(max_length=64)
    label_key = serializers.CharField(max_length=256)
    field_type = serializers.ChoiceField(choices=CustomFieldType.choices)
    is_required = serializers.BooleanField(required=False, default=False)
    sort_order = serializers.IntegerField(required=False, default=0, min_value=0)
    validation = serializers.DictField(required=False, default=dict)
    options = serializers.ListField(required=False, default=list)
    help_text = serializers.CharField(required=False, default="", allow_blank=True, max_length=512)


class CustomFieldValuesSerializer(serializers.Serializer):
    """Input/output for GET/PUT ``.../custom-fields/``.

    Accepts and returns a flat ``{key: value}`` dict.
    """

    def to_representation(self, instance: dict) -> dict:
        return instance

    def to_internal_value(self, data):
        if not isinstance(data, dict):
            raise serializers.ValidationError("Expected a JSON object.")
        return data


# ---------------------------------------------------------------------------
# Tags
# ---------------------------------------------------------------------------


class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model = Tag
        fields = ("id", "name", "slug", "color", "entity_type", "created_by_id")
        read_only_fields = ("id", "created_by_id")


class TagCreateSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=64)
    slug = serializers.SlugField(max_length=64, required=False, allow_blank=True, default="")
    color = serializers.CharField(max_length=7, required=False, default="#6B7280")
    entity_type = serializers.CharField(max_length=128, required=False, default="", allow_blank=True)


class TagAssignSerializer(serializers.Serializer):
    """Input for POST ``.../tags/`` (assign tags to an entity)."""

    tag_ids = serializers.ListField(
        child=serializers.IntegerField(min_value=1),
        min_length=1,
    )


class TagAssignmentSerializer(serializers.ModelSerializer):
    tag = TagSerializer(read_only=True)

    class Meta:
        model = TagAssignment
        fields = ("id", "tag", "assigned_by_id", "created_at")
        read_only_fields = fields


# ---------------------------------------------------------------------------
# Notes
# ---------------------------------------------------------------------------


class NoteSerializer(serializers.ModelSerializer):
    author = serializers.SerializerMethodField()

    class Meta:
        model = Note
        fields = (
            "id",
            "body",
            "visibility",
            "author",
            "is_pinned",
            "edited_at",
            "created_at",
            "updated_at",
        )
        read_only_fields = fields

    def get_author(self, obj):
        if obj.author:
            return {"id": obj.author_id, "name": str(obj.author)}
        return None


class NoteCreateSerializer(serializers.Serializer):
    """Input schema for ``POST .../notes/``."""

    body = serializers.CharField(min_length=1)
    visibility = serializers.ChoiceField(
        choices=NoteVisibility.choices,
        default=NoteVisibility.PRIVATE,
        required=False,
    )


class NoteEditSerializer(serializers.Serializer):
    """Input schema for ``PATCH .../notes/{note_id}/``."""

    body = serializers.CharField(min_length=1, required=False)
    is_pinned = serializers.BooleanField(required=False)

    def validate(self, attrs):
        if not attrs:
            raise serializers.ValidationError(
                "Provide at least one of: body, is_pinned."
            )
        return attrs


# ---------------------------------------------------------------------------
# Dynamic Forms
# ---------------------------------------------------------------------------


class FormDefinitionSerializer(serializers.ModelSerializer):
    created_by_id = serializers.IntegerField(read_only=True)

    class Meta:
        model = FormDefinition
        fields = (
            "id",
            "title",
            "description",
            "entity_type",
            "fields",
            "submit_action",
            "submit_config",
            "is_active",
            "public_url_slug",
            "created_by_id",
            "created_at",
            "updated_at",
        )
        read_only_fields = ("id", "created_by_id", "created_at", "updated_at")


class FormDefinitionCreateSerializer(serializers.Serializer):
    """Input schema for ``POST /api/v1/forms/definitions/``."""

    title = serializers.CharField(max_length=256)
    description = serializers.CharField(required=False, default="", allow_blank=True)
    entity_type = serializers.CharField(
        max_length=128, required=False, default="", allow_blank=True
    )
    fields = serializers.ListField(
        child=serializers.DictField(),
        required=False,
        default=list,
    )
    submit_action = serializers.ChoiceField(
        choices=["notify_only", "webhook", "create_ticket", "create_lead"],
        required=False,
        default="notify_only",
    )
    submit_config = serializers.DictField(required=False, default=dict)
    is_active = serializers.BooleanField(required=False, default=True)
    public_url_slug = serializers.SlugField(
        max_length=128, required=False, default="", allow_blank=True
    )


class FormDefinitionUpdateSerializer(serializers.Serializer):
    """Input schema for ``PATCH /api/v1/forms/definitions/{id}/``."""

    title = serializers.CharField(max_length=256, required=False)
    description = serializers.CharField(required=False, allow_blank=True)
    entity_type = serializers.CharField(
        max_length=128, required=False, allow_blank=True
    )
    fields = serializers.ListField(child=serializers.DictField(), required=False)
    submit_action = serializers.ChoiceField(
        choices=["notify_only", "webhook", "create_ticket", "create_lead"],
        required=False,
    )
    submit_config = serializers.DictField(required=False)
    is_active = serializers.BooleanField(required=False)
    public_url_slug = serializers.SlugField(
        max_length=128, required=False, allow_blank=True
    )


class FormSubmissionSerializer(serializers.ModelSerializer):
    submitted_by = serializers.SerializerMethodField()

    class Meta:
        model = FormSubmission
        fields = (
            "id",
            "form_id",
            "data",
            "submitted_by",
            "ip_address",
            "processed",
            "created_at",
        )
        read_only_fields = fields

    def get_submitted_by(self, obj):
        if obj.submitted_by:
            return {"id": obj.submitted_by_id, "name": str(obj.submitted_by)}
        return None


class FormSubmitSerializer(serializers.Serializer):
    """Input schema for ``POST /api/v1/forms/definitions/{id}/submit/``."""

    data = serializers.DictField()


# ---------------------------------------------------------------------------
# Import Engine
# ---------------------------------------------------------------------------


class ImportJobSerializer(serializers.ModelSerializer):
    """Read-only representation of an :class:`ImportJob`."""

    created_by = serializers.SerializerMethodField()

    class Meta:
        model = ImportJob
        fields = (
            "id",
            "public_id",
            "entity_type",
            "status",
            "total_rows",
            "processed_rows",
            "error_rows",
            "started_at",
            "finished_at",
            "created_by",
            "created_at",
        )
        read_only_fields = fields

    def get_created_by(self, obj):
        if obj.created_by:
            return {"id": obj.created_by_id, "name": str(obj.created_by)}
        return None


class ImportJobErrorSerializer(serializers.ModelSerializer):
    """Representation that includes the full error_report."""

    class Meta:
        model = ImportJob
        fields = (
            "id",
            "entity_type",
            "status",
            "total_rows",
            "processed_rows",
            "error_rows",
            "error_report",
        )
        read_only_fields = fields


class ImportJobCreateSerializer(serializers.Serializer):
    """Input schema for ``POST /api/v1/import/{entity_type}/``."""

    file_id = serializers.IntegerField(min_value=1)


# ---------------------------------------------------------------------------
# Export Engine
# ---------------------------------------------------------------------------


class ExportJobSerializer(serializers.ModelSerializer):
    """Read-only representation of an :class:`ExportJob`."""

    created_by = serializers.SerializerMethodField()
    file_url = serializers.SerializerMethodField()

    class Meta:
        model = ExportJob
        fields = (
            "id",
            "public_id",
            "entity_type",
            "format",
            "filters",
            "status",
            "row_count",
            "error_message",
            "started_at",
            "finished_at",
            "created_by",
            "file_url",
            "created_at",
        )
        read_only_fields = fields

    def get_created_by(self, obj):
        if obj.created_by:
            return {"id": obj.created_by_id, "name": str(obj.created_by)}
        return None

    def get_file_url(self, obj):
        """Return a public download URL when the export file is ready."""
        if obj.file_id:
            return f"/api/v1/export/jobs/{obj.pk}/download/"
        return None


class ExportJobCreateSerializer(serializers.Serializer):
    """Input schema for ``POST /api/v1/export/{entity_type}/``."""

    format = serializers.ChoiceField(
        choices=ExportFormat.choices,
        default=ExportFormat.CSV,
    )
    filters = serializers.DictField(required=False, default=dict)


# ---------------------------------------------------------------------------
# Print Engine
# ---------------------------------------------------------------------------


class PrintTemplateSerializer(serializers.ModelSerializer):
    """Read serializer for :class:`PrintTemplate`."""

    created_by = serializers.SerializerMethodField()

    class Meta:
        model = PrintTemplate
        fields = (
            "id",
            "public_id",
            "entity_type",
            "name",
            "html_template",
            "css",
            "paper_size",
            "orientation",
            "is_active",
            "created_by",
            "created_at",
            "updated_at",
        )
        read_only_fields = fields

    def get_created_by(self, obj):
        if obj.created_by:
            return {"id": obj.created_by_id, "name": str(obj.created_by)}
        return None


class PrintTemplateCreateSerializer(serializers.Serializer):
    """Input schema for ``POST /api/v1/print/templates/``."""

    entity_type = serializers.CharField(max_length=128)
    name = serializers.CharField(max_length=255)
    html_template = serializers.CharField()
    css = serializers.CharField(required=False, default="", allow_blank=True)
    paper_size = serializers.ChoiceField(
        choices=PaperSize.choices,
        default=PaperSize.A4,
    )
    orientation = serializers.ChoiceField(
        choices=PageOrientation.choices,
        default=PageOrientation.PORTRAIT,
    )


class PrintTemplateUpdateSerializer(serializers.Serializer):
    """Input schema for ``PATCH /api/v1/print/templates/{id}/``."""

    name = serializers.CharField(max_length=255, required=False)
    html_template = serializers.CharField(required=False)
    css = serializers.CharField(required=False, allow_blank=True)
    paper_size = serializers.ChoiceField(choices=PaperSize.choices, required=False)
    orientation = serializers.ChoiceField(choices=PageOrientation.choices, required=False)
    is_active = serializers.BooleanField(required=False)


class PrintPreviewRequestSerializer(serializers.Serializer):
    """Input schema for ``POST .../preview/`` — accepts an arbitrary entity dict."""

    entity = serializers.DictField(
        required=False,
        default=dict,
        help_text="Key-value pairs passed as {{ entity.key }} in the template.",
    )
    extra_context = serializers.DictField(required=False, default=dict)


# ---------------------------------------------------------------------------
# Template Engine (Message Templates)
# ---------------------------------------------------------------------------


class MessageTemplateSerializer(serializers.ModelSerializer):
    """Read-only representation of a :class:`MessageTemplate`."""

    created_by = serializers.SerializerMethodField()

    class Meta:
        model = MessageTemplate
        fields = (
            "id",
            "public_id",
            "code",
            "name",
            "channel",
            "subject",
            "body",
            "variables",
            "language",
            "is_active",
            "created_by",
            "created_at",
            "updated_at",
        )
        read_only_fields = fields

    def get_created_by(self, obj):
        if obj.created_by:
            return {"id": obj.created_by_id, "name": str(obj.created_by)}
        return None


class MessageTemplateCreateSerializer(serializers.Serializer):
    """Input schema for POST /api/v1/templates/messages/."""

    code = serializers.SlugField(max_length=128)
    name = serializers.CharField(max_length=255)
    channel = serializers.ChoiceField(choices=MessageChannel.choices, default=MessageChannel.EMAIL)
    subject = serializers.CharField(max_length=512, required=False, default="", allow_blank=True)
    body = serializers.CharField()
    variables = serializers.ListField(
        child=serializers.DictField(),
        required=False,
        default=list,
    )
    language = serializers.CharField(max_length=16, required=False, default="en")


class MessageTemplateUpdateSerializer(serializers.Serializer):
    """Input schema for PATCH /api/v1/templates/messages/{id}/."""

    name = serializers.CharField(max_length=255, required=False)
    channel = serializers.ChoiceField(choices=MessageChannel.choices, required=False)
    subject = serializers.CharField(max_length=512, required=False, allow_blank=True)
    body = serializers.CharField(required=False)
    variables = serializers.ListField(child=serializers.DictField(), required=False)
    language = serializers.CharField(max_length=16, required=False)
    is_active = serializers.BooleanField(required=False)


class MessageTemplatePreviewSerializer(serializers.Serializer):
    """Input schema for POST /api/v1/templates/messages/{id}/preview/."""

    context = serializers.DictField(required=False, default=dict)
    language = serializers.CharField(max_length=16, required=False, default="en")
