from django import forms
from django.core.exceptions import ValidationError
from .models import TradeOffer, TradeAcceptance
from accounts.models import FriendCode
from cards.models import Card
from django.forms import ModelForm
from trades.models import TradeOfferHaveCard, TradeOfferWantCard

class NoValidationMultipleChoiceField(forms.MultipleChoiceField):
    def validate(self, value):
        # Override the validation to skip checking against defined choices
        pass

class TradeOfferAcceptForm(forms.Form):
    friend_code = forms.ModelChoiceField(
        queryset=FriendCode.objects.none(),
        label="Select a Friend Code to Accept This Trade Offer"
    )

    def __init__(self, *args, **kwargs):
        # Expecting a keyword argument `friend_codes` with the user's friend codes.
        friend_codes = kwargs.pop("friend_codes")
        super().__init__(*args, **kwargs)
        self.fields["friend_code"].queryset = friend_codes

class TradeAcceptanceCreateForm(forms.ModelForm):
    """
    Form for creating a TradeAcceptance.
    Expects the caller to pass:
      - trade_offer: the instance of TradeOffer this acceptance is for.
      - friend_codes: a queryset of FriendCode objects for the current user.
      - default_friend_code (optional): the user's default FriendCode.
    It filters available requested and offered cards based on what's still available.
    """
    class Meta:
        model = TradeAcceptance
        fields = ["accepted_by", "requested_card", "offered_card"]

    def __init__(self, *args, trade_offer=None, friend_codes=None, default_friend_code=None, **kwargs):
        if trade_offer is None:
            raise ValueError("trade_offer must be provided to filter choices.")
        super().__init__(*args, **kwargs)
        self.trade_offer = trade_offer

        if friend_codes is None:
            raise ValueError("friend_codes must be provided")
        # Set the accepted_by queryset to the user's friend codes.
        self.fields["accepted_by"].queryset = friend_codes

        # If the user only has one friend code, preset the field and use a HiddenInput.
        if friend_codes.count() == 1:
            self.initial["accepted_by"] = friend_codes.first().pk
            self.fields["accepted_by"].widget = forms.HiddenInput()
        # Otherwise, if a default friend code is provided and it is in the queryset, preselect it.
        elif default_friend_code and friend_codes.filter(pk=default_friend_code.pk).exists():
            self.initial["accepted_by"] = default_friend_code.pk

        # Update available requested_card choices from the TradeOffer's "have" side.
        active_states = [
            TradeAcceptance.AcceptanceState.ACCEPTED,
            TradeAcceptance.AcceptanceState.SENT,
            TradeAcceptance.AcceptanceState.RECEIVED,
        ]
        self.fields["requested_card"].queryset = trade_offer.have_cards_available_qs

        self.fields["offered_card"].queryset = trade_offer.want_cards_available_qs

    def clean(self):
        """
        Ensure the instance has its trade_offer set before model-level validation occurs.
        This prevents errors when the model clean() method attempts to access trade_offer.
        """
        self.instance.trade_offer = self.trade_offer
        return super().clean()

class ButtonRadioSelect(forms.RadioSelect):
    template_name = "widgets/button_radio_select.html"

class TradeAcceptanceTransitionForm(forms.Form):
    state = forms.ChoiceField(widget=forms.HiddenInput())

    def __init__(self, *args, instance=None, user=None, **kwargs):
        """
        Initializes the form with allowed transitions from the provided instance.
        :param instance: A TradeAcceptance instance.
        """
        super().__init__(*args, **kwargs)
        if instance is None:
            raise ValueError("A TradeAcceptance instance must be provided")
        self.instance = instance
        self.user = user
        
        self.fields["state"].choices = instance.get_allowed_state_transitions(user)

class TradeOfferCreateForm(ModelForm):
    # Override the default fields to capture quantity info in the format 'card_id:quantity'
    have_cards = NoValidationMultipleChoiceField(widget=forms.SelectMultiple, required=True)
    want_cards = NoValidationMultipleChoiceField(widget=forms.SelectMultiple, required=True)

    class Meta:
        model = TradeOffer
        fields = ["want_cards", "have_cards", "initiated_by"]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Populate choices from Card model using the new field 'rarity_level' instead of the removed relation.
        cards = Card.objects.order_by("name", "rarity_level")
        choices = [(str(card.pk), card.name) for card in cards]
        self.fields["have_cards"].choices = choices
        self.fields["want_cards"].choices = choices

    def clean_have_cards(self):
        data = self.data.getlist("have_cards")
        parsed = {}
        for item in data:
            if ':' not in item:
                # Ignore any input without a colon.
                continue
            parts = item.split(':')
            card_id = parts[0]
            try:
                # Only parse quantity when a colon is present.
                quantity = int(parts[1])
            except ValueError:
                raise forms.ValidationError(f"Invalid quantity provided in {item}")
            parsed[card_id] = parsed.get(card_id, 0) + quantity
        # Validate that each have card does not exceed the max quantity of 20.
        for card_id, quantity in parsed.items():
            if quantity > 20:
                card = Card.objects.get(pk=card_id)
                raise forms.ValidationError(
                    f"Maximum allowed quantity for each have card is 20. Card {card} has {quantity}."
                )
        # Ensure no more than 20 unique have cards are selected.
        if len(parsed) > 20:
            raise forms.ValidationError("You can only select a maximum of 20 unique have cards.")
        return parsed

    def clean_want_cards(self):
        data = self.data.getlist("want_cards")
        parsed = {}
        for item in data:
            if ':' not in item:
                continue
            parts = item.split(':')
            card_id = parts[0]
            try:
                quantity = int(parts[1])
            except ValueError:
                raise forms.ValidationError(f"Invalid quantity provided in {item}")
            parsed[card_id] = parsed.get(card_id, 0) + quantity
        # Validate that each want card does not exceed the max quantity of 20.
        for card_id, quantity in parsed.items():
            if quantity > 20:
                # look up card name
                card = Card.objects.get(pk=card_id)
                raise forms.ValidationError(
                    f"Maximum allowed quantity for each want card is 20. Card {card} has {quantity}."
                )
        # Ensure no more than 20 unique want cards are selected.
        if len(parsed) > 20:
            raise forms.ValidationError("You can only select a maximum of 20 unique want cards.")
        return parsed

    def save(self, commit=True):
        instance = super().save(commit=False)
        if commit:
            instance.save()
        # Clear any existing through model entries in case of update
        TradeOfferHaveCard.objects.filter(trade_offer=instance).delete()
        TradeOfferWantCard.objects.filter(trade_offer=instance).delete()
        
        # Create through entries for have_cards
        for card_id, quantity in self.cleaned_data["have_cards"].items():
            card = Card.objects.get(pk=card_id)
            TradeOfferHaveCard.objects.create(trade_offer=instance, card=card, quantity=quantity)

        # Create through entries for want_cards
        for card_id, quantity in self.cleaned_data["want_cards"].items():
            card = Card.objects.get(pk=card_id)
            TradeOfferWantCard.objects.create(trade_offer=instance, card=card, quantity=quantity)

        return instance
