from django import forms
from django.core.exceptions import ValidationError
from django.contrib.auth import get_user_model
from decimal import Decimal
from django.utils import timezone
from django.contrib.contenttypes.models import ContentType

from .models import (
    MaterialCategory,
    Material,
    MaterialImage,
    ProvidedService,
    Customer,
    CustomerSite,
    MaterialEmissionFactor,
    PickupItem,
)
from markdownfield.widgets import MDEWidget
from orgs.models import UserProfile


class MaterialCategoryForm(forms.ModelForm):
    class Meta:
        model = MaterialCategory
        fields = ["organization", "name"]


class MultiFileInput(forms.ClearableFileInput):
    allow_multiple_selected = True


class MultiImageField(forms.Field):
    widget = MultiFileInput

    def __init__(self, *args, **kwargs):
        kwargs.setdefault("required", False)
        super().__init__(*args, **kwargs)

    def to_python(self, data):
        return data

    def validate(self, value):
        # Basic required check; skip per-file validation here
        if self.required and not value:
            raise ValidationError("This field is required.")


class MaterialForm(forms.ModelForm):
    images = MultiImageField(help_text="Upload one or more sample images (optional)")

    class Meta:
        model = Material
        fields = ["organization", "category", "name", "code", "default_unit", "images"]

    def save(self, commit=True):
        instance = super().save(commit=commit)
        files = self.files.getlist("images") if hasattr(self, "files") else []
        if commit and files:
            # Instance has a PK; we can create images now
            order_start = instance.images.count()
            for i, f in enumerate(files):
                MaterialImage.objects.create(material=instance, image=f, display_order=order_start + i)
        else:
            # Defer image saving until caller completes save
            self._pending_images = files
        return instance

    def save_images(self, instance: Material | None = None):
        """Persist any pending images after the Material has been saved."""
        if not hasattr(self, "_pending_images"):
            return
        target = instance or getattr(self, "instance", None)
        if not target or not getattr(target, "pk", None):
            return
        order_start = target.images.count()
        for i, f in enumerate(self._pending_images or []):
            MaterialImage.objects.create(material=target, image=f, display_order=order_start + i)
        # Clear pending list
        self._pending_images = []


class CustomerForm(forms.ModelForm):
    class Meta:
        model = Customer
        fields = ["organization", "name", "email", "phone", "billing_address", "price_list"]


class CustomerSiteForm(forms.ModelForm):
    class Meta:
        model = CustomerSite
        fields = ["customer", "name", "address", "contact_name", "contact_phone", "contact_email"]


class ProvidedServiceForm(forms.ModelForm):
    class Meta:
        model = ProvidedService
        fields = ["title", "description", "body", "display_order", "is_enabled"]
        widgets = {
            "description": forms.Textarea(attrs={"rows": 3}),
            "body": MDEWidget(),
        }


# Operational forms ----------------------------------------------------------

User = get_user_model()


class PickupAssignForm(forms.Form):
    driver = forms.ModelChoiceField(queryset=User.objects.all(), required=True, label="Assign Driver")


class PickupStatusForm(forms.Form):
    status = forms.ChoiceField(choices=(), required=True, label="Set Status")

    def __init__(self, *args, **kwargs):
        from .models import PickupOrder

        super().__init__(*args, **kwargs)
        self.fields["status"].choices = PickupOrder.STATUS_CHOICES


class PaymentForm(forms.Form):
    amount = forms.DecimalField(max_digits=14, decimal_places=2)
    received_at = forms.DateTimeField(required=False, initial=timezone.now)
    reference = forms.CharField(max_length=128, required=False)


class DocumentForm(forms.Form):
    organization = forms.ModelChoiceField(queryset=None)
    file = forms.FileField()
    kind = forms.CharField(max_length=64, required=False)
    content_type = forms.ModelChoiceField(queryset=ContentType.objects.all(), required=True, label="Attach To Model")
    object_id = forms.IntegerField(required=True, label="Object ID")

    def __init__(self, *args, **kwargs):
        from orgs.models import Organization as Org

        super().__init__(*args, **kwargs)
        self.fields["organization"].queryset = Org.objects.all()


# User management ------------------------------------------------------------


class UserCreateForm(forms.Form):
    username = forms.CharField(max_length=150)
    email = forms.EmailField(required=False)
    first_name = forms.CharField(max_length=150, required=False)
    last_name = forms.CharField(max_length=150, required=False)
    role = forms.ChoiceField(choices=UserProfile.ROLE_CHOICES)
    password1 = forms.CharField(widget=forms.PasswordInput)
    password2 = forms.CharField(widget=forms.PasswordInput)

    def clean_username(self):
        User = get_user_model()
        username = self.cleaned_data["username"].strip()
        if User.objects.filter(username__iexact=username).exists():
            raise forms.ValidationError("Username already taken")
        return username

    def clean(self):
        cleaned = super().clean()
        p1 = cleaned.get("password1")
        p2 = cleaned.get("password2")
        if p1 and p2 and p1 != p2:
            self.add_error("password2", "Passwords do not match")
        return cleaned


class UserEditForm(forms.Form):
    email = forms.EmailField(required=False)
    first_name = forms.CharField(max_length=150, required=False)
    last_name = forms.CharField(max_length=150, required=False)
    role = forms.ChoiceField(choices=UserProfile.ROLE_CHOICES)
    is_active = forms.BooleanField(required=False, label="Active")
    password1 = forms.CharField(widget=forms.PasswordInput, required=False)
    password2 = forms.CharField(widget=forms.PasswordInput, required=False)

    def clean(self):
        cleaned = super().clean()
        p1 = cleaned.get("password1")
        p2 = cleaned.get("password2")
        if (p1 or p2) and p1 != p2:
            self.add_error("password2", "Passwords do not match")
        return cleaned


class UserProfilePhotoForm(forms.ModelForm):
    remove_photo = forms.BooleanField(label="Remove current photo", required=False)

    class Meta:
        model = UserProfile
        fields = ["my_photo", "remove_photo"]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["my_photo"].widget = forms.ClearableFileInput(attrs={
            "accept": "image/*",
            "class": "hidden",
            "id": "id_my_photo",
        })

    def clean_my_photo(self):
        f = self.cleaned_data.get("my_photo")
        if not f:
            return f
        content_type = getattr(f, "content_type", "")
        if content_type and not content_type.startswith("image/"):
            raise ValidationError("Please upload an image file.")
        max_bytes = 5 * 1024 * 1024
        if getattr(f, "size", 0) and f.size > max_bytes:
            raise ValidationError("Image too large (max 5MB).")
        return f

    def save(self, commit: bool = True):
        instance: UserProfile = super().save(commit=False)
        remove = self.cleaned_data.get("remove_photo", False)
        new_file = self.cleaned_data.get("my_photo")
        if remove and not new_file:
            try:
                if instance.my_photo:
                    instance.my_photo.delete(save=False)
            except Exception:
                pass
            instance.my_photo = None
        if commit:
            instance.save()
        return instance


# Weighing -------------------------------------------------------------------


class WeighTicketForm(forms.Form):
    ticket_number = forms.CharField(max_length=64, required=False)
    gross_weight = forms.DecimalField(max_digits=12, decimal_places=3)
    tare_weight = forms.DecimalField(max_digits=12, decimal_places=3)
    net_weight = forms.DecimalField(max_digits=12, decimal_places=3, required=False, help_text="Auto-calculated if left blank")
    unit = forms.ChoiceField(choices=(), required=True)

    def __init__(self, *args, **kwargs):
        from .models import Material

        super().__init__(*args, **kwargs)
        self.fields["unit"].choices = Material.UNIT_CHOICES

    def clean(self):
        cleaned = super().clean()
        gross = cleaned.get("gross_weight")
        tare = cleaned.get("tare_weight")
        net = cleaned.get("net_weight")
        if gross is not None and tare is not None and net is None:
            cleaned["net_weight"] = (Decimal(gross) - Decimal(tare)).quantize(Decimal("0.001"))
        return cleaned


class WeighLineForm(forms.Form):
    material = forms.ModelChoiceField(queryset=Material.objects.none())
    quantity = forms.DecimalField(max_digits=12, decimal_places=3)
    unit = forms.ChoiceField(choices=(), required=True)

    def __init__(self, *args, **kwargs):
        org = kwargs.pop("organization", None)
        super().__init__(*args, **kwargs)
        from .models import Material as Mat
        self.fields["unit"].choices = Mat.UNIT_CHOICES
        # Scope materials by organization if provided
        if org is not None:
            self.fields["material"].queryset = Material.objects.filter(organization=org).order_by("name")
        else:
            self.fields["material"].queryset = Material.objects.all().order_by("name")


class MaterialEmissionFactorForm(forms.ModelForm):
    class Meta:
        model = MaterialEmissionFactor
        fields = [
            "organization",
            "material",
            "unit",
            "kgco2e_per_unit",
            "source",
            "effective_from",
            "effective_to",
        ]


class PickupItemForm(forms.Form):
    material = forms.ModelChoiceField(queryset=Material.objects.none())
    estimated_qty = forms.DecimalField(max_digits=12, decimal_places=3, required=False)
    unit = forms.ChoiceField(choices=(), required=True)

    def __init__(self, *args, **kwargs):
        org = kwargs.pop("organization", None)
        super().__init__(*args, **kwargs)
        from .models import Material as Mat
        self.fields["unit"].choices = Mat.UNIT_CHOICES
        if org is not None:
            self.fields["material"].queryset = Material.objects.filter(organization=org).order_by("name")
        else:
            self.fields["material"].queryset = Material.objects.all().order_by("name")


class WeighTicketWeightsForm(forms.Form):
    gross_weight = forms.DecimalField(max_digits=12, decimal_places=3)
    tare_weight = forms.DecimalField(max_digits=12, decimal_places=3)
    unit = forms.ChoiceField(choices=(), required=True)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        from .models import Material as Mat
        self.fields["unit"].choices = Mat.UNIT_CHOICES

    def clean(self):
        cleaned = super().clean()
        g = cleaned.get("gross_weight")
        t = cleaned.get("tare_weight")
        if g is not None and t is not None and g < t:
            self.add_error("gross_weight", "Gross must be greater than or equal to tare")
        return cleaned
