from django import forms
from django.utils.translation import gettext_lazy as _
from django.core.validators import URLValidator
from urllib.parse import quote
import re

from .models import Link, Page, Post, ImageCollection, Image, Tag
from simplemde.fields import SimpleMDEField


class TagInputField(forms.ModelMultipleChoiceField):
    """A ModelMultipleChoiceField for tags that accepts:
      - existing PKs (as normal), and
      - raw text strings for tags that don't yet exist (created on save).

    Select2 foreground config (`tags: true` + `createTag`) submits the
    raw text as the value for free-typed tags. This field treats any
    non-numeric submitted value as a new tag name.
    """

    def to_python(self, value):
        if value in self.empty_values:
            return None
        raw = str(value).strip()
        if not raw:
            return None
        # Existing tags are submitted as their PK
        if raw.isdigit():
            return self.queryset.filter(pk=int(raw)).first()
        # New-tag text: lowercase so the tag namespace stays consistent and
        # case-insensitive duplicates are avoided.
        name = raw.lower()
        tag, _ = Tag.objects.get_or_create(
            name__iexact=name,
            defaults={'name': name},
        )
        return tag

    def _check_values(self, value):
        """Override the strict PK-only queryset filter so mixed lists of
        existing PKs and new-tag text resolve cleanly to Tag objects."""
        if not value:
            return []
        result = []
        for single in value:
            tag = self.to_python(single)
            if tag is not None:
                result.append(tag)
        return result


class LinkForm(forms.ModelForm):
    text = SimpleMDEField()
    # 动作链接的配置字段（不入模型，clean 时组装进 action_config JSON）
    action_type = forms.ChoiceField(
        required=False,
        choices=[('discord_send', _('发消息到 Discord'))],
        label=_('动作'),
        initial='discord_send',
        widget=forms.RadioSelect(attrs={'class': 'action-type-radio'}),
        help_text=_('打开链接时执行的动作'),
    )
    action_message_template = forms.CharField(
        required=False,
        label=_('消息内容'),
        help_text=_('链接地址后面加的字会自动放在 {query} 的位置。例如链接 go/ask/今天天气 → 消息会是“@小黑 今天天气”'),
        widget=forms.Textarea(attrs={'rows': 2, 'placeholder': '@小黑 帮我看看 {query}',
                                     'class': 'shadow appearance-none rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'}),
    )

    class Meta:
        model = Link
        fields = ['alias', 'original_url', 'link_type', 'text', 'description', 'tags', 'action_config']
        widgets = {
            'link_type': forms.RadioSelect(),
            'description': forms.Textarea(attrs={'rows': 3}),
            'tags': forms.SelectMultiple(attrs={'class': 'select2'}),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Replace the auto-generated ModelMultipleChoiceField with our
        # tolerant variant so newly-typed tags don't fail validation
        # before clean_tags() runs.
        self.fields['tags'] = TagInputField(
            queryset=Tag.objects.all(),
            required=False,
            widget=forms.SelectMultiple(attrs={'class': 'select2'}),
        )
        # 编辑已有 action 链接时，从 action_config 预填动作配置字段
        if self.instance and self.instance.pk and self.instance.action_config:
            cfg = self.instance.action_config
            self.fields['action_type'].initial = cfg.get('action_type', '')
            self.fields['action_message_template'].initial = cfg.get('message_template', '')

    def clean_original_url(self):
        url = self.cleaned_data.get('original_url')
        if not url:
            return url

        # Extract template parameters
        template_params = re.findall(r'\{([^{}]*)\}', url)

        # Temporarily replace template parameters with placeholder
        temp_url = url
        for param in template_params:
            param_full = '{' + param + '}'
            temp_url = temp_url.replace(param_full, 'template-param')

        # Validate the URL with placeholders
        # Encode non-ASCII characters and spaces so that static Unicode text
        # (e.g. Chinese characters) mixed with template params passes URLValidator.
        # The safe string keeps all standard URL characters intact and includes '%'
        # to avoid double-encoding any already percent-encoded sequences.
        try:
            encoded_url = quote(temp_url, safe="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=%")
            URLValidator()(encoded_url)
        except forms.ValidationError:
            raise forms.ValidationError(_("Please enter a valid URL. Template parameters are allowed in the format {param_name,default=value}."))

        return url

    def clean(self):
        cleaned_data = super().clean()
        link_type = cleaned_data.get('link_type')
        original_url = cleaned_data.get('original_url')
        text = cleaned_data.get('text')
        action_type = cleaned_data.get('action_type')
        action_template = (cleaned_data.get('action_message_template') or '').strip()

        if link_type == Link.LinkType.LINK and not original_url:
            raise forms.ValidationError(_("Original URL is required for Link type."))
        elif link_type == Link.LinkType.CUSTOM and not text:
            raise forms.ValidationError(_("Text is required for Custom type."))
        elif link_type == Link.LinkType.ACTION:
            if not action_type:
                raise forms.ValidationError(_("Please choose an action for Action type."))
            if not action_template:
                raise forms.ValidationError(_("Message template is required for Action type."))
            # 组装 action_config JSON
            cleaned_data['action_config'] = {
                'action_type': action_type,
                'message_template': action_template,
            }
        else:
            # 非动作类型清空 action_config
            cleaned_data['action_config'] = {}

        return cleaned_data

class PageForm(forms.ModelForm):
    class Meta:
        model = Page
        fields = ['url', 'title', 'summary', 'content', 'tags']
        widgets = {
            'summary': forms.Textarea(attrs={'rows': 3}),
            'content': forms.Textarea(attrs={'rows': 10}),
            'tags': forms.SelectMultiple(attrs={'class': 'select2'}),
        }

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ['title', 'summary', 'content', 'tags']
        widgets = {
            'summary': forms.Textarea(attrs={'rows': 3, 'class': 'mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm'}),
            'content': forms.Textarea(attrs={'rows': 20}),
            'tags': forms.SelectMultiple(attrs={'class': 'select2'}),
        }

class MultipleFileInput(forms.ClearableFileInput):
    allow_multiple_selected = True

class MultipleFileField(forms.FileField):
    def __init__(self, *args, **kwargs):
        kwargs.setdefault("widget", MultipleFileInput())
        super().__init__(*args, **kwargs)

    def clean(self, data, initial=None):
        single_file_clean = super().clean
        if isinstance(data, (list, tuple)):
            result = [single_file_clean(d, initial) for d in data]
        else:
            result = single_file_clean(data, initial)
        return result

class ImageCollectionForm(forms.ModelForm):
    class Meta:
        model = ImageCollection
        fields = ['name', 'description', 'tags']
        widgets = {
            'description': forms.Textarea(attrs={'rows': 3}),
            'tags': forms.SelectMultiple(attrs={'class': 'select2'}),
        }

class ImageUploadForm(forms.ModelForm):
    files = MultipleFileField(
        widget=MultipleFileInput(attrs={
            'accept': 'image/*',
            'class': 'hidden',
        })
    )

    class Meta:
        model = Image
        fields = ['title', 'description']

class TagForm(forms.ModelForm):
    class Meta:
        model = Tag
        fields = ['name', 'description']
        widgets = {
            'name': forms.TextInput(attrs={'class': 'mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm'}),
            'description': forms.Textarea(attrs={'rows': 3, 'class': 'mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm'}),
        }


