from __future__ import annotations

from pathlib import Path
from decimal import Decimal, ROUND_HALF_UP

from django import forms
from django.forms import BaseInlineFormSet, inlineformset_factory
from django.utils.dateparse import parse_date
from django.utils import timezone

from accounts.forms import BaseStyledFormMixin

from .document_processing import normalize_uploaded_file_to_pdf
from .models import (
    VAT_RATE,
    VerificationClaim,
    VerificationDescriptionOption,
    VerificationInvoice,
    VerificationInvoiceDocument,
)

MAX_DOCUMENT_UPLOAD_BYTES = 2 * 1024 * 1024
REQUIRED_FIELD_MESSAGE = "Required"
FUTURE_DATE_MESSAGE = "Future date not allowed"


def _vat_amount_for_total(total_amount) -> Decimal | None:
    if total_amount in (None, ""):
        return None
    return (Decimal(total_amount) * VAT_RATE).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)


def _apply_invoice_business_rules(*, cleaned_data: dict, claim_date_received=None) -> dict[str, str]:
    errors: dict[str, str] = {}
    total_amount_paid = cleaned_data.get("total_amount_paid")
    vat_approved = cleaned_data.get("vat_approved")
    vat_paid = cleaned_data.get("vat_paid")
    transaction_date = cleaned_data.get("transaction_date")
    today = timezone.localdate()
    vat_amount = _vat_amount_for_total(total_amount_paid)

    if vat_amount is not None and vat_approved not in (None, "") and vat_approved > vat_amount:
        errors["vat_approved"] = "Exceeds VAT amount"
    if vat_amount is not None and vat_paid not in (None, "") and vat_paid > vat_amount:
        errors["vat_paid"] = "Exceeds VAT amount"
    if transaction_date and transaction_date > today:
        errors["transaction_date"] = FUTURE_DATE_MESSAGE
    elif claim_date_received and transaction_date and transaction_date > claim_date_received:
        errors["transaction_date"] = "After date received"

    return errors


def _apply_claim_date_rules(*, cleaned_data: dict, field_names: list[str]) -> dict[str, str]:
    errors: dict[str, str] = {}
    today = timezone.localdate()
    for field_name in field_names:
        value = cleaned_data.get(field_name)
        if value and value > today:
            errors[field_name] = FUTURE_DATE_MESSAGE
    return errors


def _validate_normalized_pdf_size(normalized_file, *, message: str):
    if normalized_file.size > MAX_DOCUMENT_UPLOAD_BYTES:
        raise forms.ValidationError(message)


class MultipleFileInput(forms.ClearableFileInput):
    allow_multiple_selected = True


class MultipleFileField(forms.FileField):
    widget = MultipleFileInput

    def clean(self, data, initial=None):
        cleaned = []
        single_clean = super().clean

        if not data:
            if self.required:
                raise forms.ValidationError("Select at least one file to upload.")
            return cleaned

        files = data if isinstance(data, (list, tuple)) else [data]
        for item in files:
            cleaned.append(single_clean(item, initial))
        return cleaned


class VerificationClaimForm(BaseStyledFormMixin, forms.ModelForm):
    class Meta:
        model = VerificationClaim
        fields = [
            "claim_reference_number",
            "date_received",
            "applicant_name_arabic",
            "applicant_name_english",
            "first_document_request_date",
            "second_reminder_date",
            "final_report_without_missing_docs_date",
            "initial_report_with_missing_docs_date",
            "followup_reminder_date",
            "final_report_submission_date",
        ]

        labels = {
            "claim_reference_number": "Claim Reference Number",
            "date_received": "Date Received",
            "applicant_name_arabic": "Applicant Name (Arabic)",
            "applicant_name_english": "Applicant Name (English)",
            "first_document_request_date": "البريد الالكتروني الأول لطلب المستندات",
            "second_reminder_date": "البريد الالكتروني الثاني للتذكير",
            "final_report_without_missing_docs_date": "اعداد / ارسال التقرير النهائي الي الهيئة ومقدم الطلب ( في حال عدم وجود مستندات ناقصة )",
            "initial_report_with_missing_docs_date": "اعداد / ارسال التقرير المبدئي الي مقدم الطلب ( في حال وجود مستندات ناقصة )",
            "followup_reminder_date": "بريد الكتروني للتذكير",
            "final_report_submission_date": "اعداد / ارسال التقرير النهائي الي الهيئة ومقدم الطلب",
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        max_date_value = timezone.localdate().isoformat()
        date_fields = [
            "date_received",
            "first_document_request_date",
            "second_reminder_date",
            "final_report_without_missing_docs_date",
            "initial_report_with_missing_docs_date",
            "followup_reminder_date",
            "final_report_submission_date",
        ]
        for field_name in date_fields:
            self.fields[field_name].widget = forms.DateInput(
                attrs={"type": "date", "class": "form-control", "max": max_date_value},
                format="%Y-%m-%d",
            )
            self.fields[field_name].input_formats = ["%Y-%m-%d"]

        for field in self.fields.values():
            if field.required:
                field.error_messages["required"] = REQUIRED_FIELD_MESSAGE

    def clean(self):
        cleaned_data = super().clean()
        date_errors = _apply_claim_date_rules(
            cleaned_data=cleaned_data,
            field_names=[
                "date_received",
                "first_document_request_date",
                "second_reminder_date",
                "final_report_without_missing_docs_date",
                "initial_report_with_missing_docs_date",
                "followup_reminder_date",
                "final_report_submission_date",
            ],
        )
        for field_name, message in date_errors.items():
            self.add_error(field_name, message)
        return cleaned_data


class DraftVerificationClaimForm(VerificationClaimForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            field.required = False
            field.widget.attrs.pop("required", None)


class VerificationInvoiceForm(BaseStyledFormMixin, forms.ModelForm):
    document_max_file_size = MAX_DOCUMENT_UPLOAD_BYTES
    invoice_files = MultipleFileField(
        required=False,
        widget=MultipleFileInput(
            attrs={
                "class": "form-control invoice-row-file-input",
                "multiple": True,
                "accept": ".pdf",
                "data-max-file-bytes": str(MAX_DOCUMENT_UPLOAD_BYTES),
                "data-max-file-message": "Each file must be 2 MB or smaller.",
            }
        ),
    )
    payment_proof_files = MultipleFileField(
        required=False,
        widget=MultipleFileInput(
            attrs={
                "class": "form-control invoice-row-file-input",
                "multiple": True,
                "accept": ".pdf",
                "data-max-file-bytes": str(MAX_DOCUMENT_UPLOAD_BYTES),
                "data-max-file-message": "Each file must be 2 MB or smaller.",
            }
        ),
    )

    class Meta:
        model = VerificationInvoice
        fields = [
            "goods_services",
            "transaction_type",
            "transaction_number",
            "transaction_date",
            "line_item",
            "supplier_trn",
            "supplier_name",
            "plot_number",
            "tos",
            "total_amount_paid",
            "vat_approved",
            "vat_paid",
            "description",
        ]

        labels = {
            "goods_services": "Goods / Services",
            "transaction_type": "Transaction Type",
            "transaction_number": "Transaction Number",
            "transaction_date": "Transaction Date",
            "line_item": "Line Item",
            "supplier_trn": "Supplier TRN",
            "supplier_name": "Supplier Name",
            "plot_number": "Plot No.",
            "tos": "TOS",
            "total_amount_paid": "Total Amount Paid (AED)",
            "vat_approved": "VAT Approved",
            "vat_paid": "VAT Paid",
            "description": "Description",
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        max_date_value = timezone.localdate().isoformat()
        self.fields["transaction_date"].widget = forms.DateInput(
            attrs={"type": "date", "class": "form-control", "max": max_date_value},
            format="%Y-%m-%d",
        )
        self.fields["transaction_date"].input_formats = ["%Y-%m-%d"]
        self.fields["goods_services"].widget.attrs.update(
            {
                "data-goods-services-shortcut": "true",
                "autocomplete": "off",
            }
        )
        self.fields["goods_services"].initial = self.instance.goods_services if self.instance.pk else ""
        self.fields["transaction_type"].initial = self.instance.transaction_type or "TAX INVOICE"
        self.fields["line_item"].required = False
        self.fields["invoice_files"].label = "Invoice Files"
        self.fields["payment_proof_files"].label = "Payment Proofs"
        self.fields["supplier_trn"].widget.attrs.update(
            {
                "inputmode": "numeric",
                "data-trn-warning": "true",
                "data-supplier-autocomplete-input": "trn",
                "autocomplete": "off",
                "spellcheck": "false",
            }
        )
        self.fields["supplier_name"].widget.attrs.update(
            {
                "data-supplier-autocomplete-input": "name",
                "autocomplete": "off",
                "spellcheck": "false",
            }
        )
        self.fields["plot_number"].widget.attrs.update({"autocomplete": "off", "spellcheck": "false"})
        self.fields["tos"].widget.attrs.update({"data-invoice-tos": "true"})
        self.fields["description"].widget = forms.TextInput(
            attrs={
                "class": "form-control",
                "data-description-input": "true",
                "list": "verification-description-options",
                "autocomplete": "off",
                "spellcheck": "false",
            }
        )
        self.fields["vat_approved"].widget.attrs["class"] = (
            f"{self.fields['vat_approved'].widget.attrs.get('class', '').strip()} vat-approved-field".strip()
        )
        for field in self.fields.values():
            if field.required:
                field.error_messages["required"] = REQUIRED_FIELD_MESSAGE

        for decimal_field in ("total_amount_paid", "vat_approved", "vat_paid"):
            self.fields[decimal_field].widget.attrs.update({"step": "0.01", "min": "0"})

        if not self.instance.pk:
            self.initial["vat_approved"] = ""
            self.initial["vat_paid"] = ""
            self.initial["description"] = ""

    def _clean_document_files(self, *, field_name: str, allowed_extensions: set[str]):
        files = self.cleaned_data.get(field_name) or []
        normalized_files = []

        for uploaded_file in files:
            extension = Path(uploaded_file.name).suffix.lower().lstrip(".")
            if extension not in allowed_extensions:
                allowed_text = ", ".join(sorted(f".{ext}" for ext in allowed_extensions))
                raise forms.ValidationError(f"Allowed file types are: {allowed_text}.")
            if uploaded_file.size > self.document_max_file_size:
                raise forms.ValidationError("Each file must be 2 MB or smaller.")
            normalized_file = normalize_uploaded_file_to_pdf(uploaded_file)
            _validate_normalized_pdf_size(normalized_file, message="Each file must be 2 MB or smaller.")
            normalized_files.append(normalized_file)

        return normalized_files

    def clean_invoice_files(self):
        return self._clean_document_files(
            field_name="invoice_files",
            allowed_extensions={"pdf"},
        )

    def clean_payment_proof_files(self):
        return self._clean_document_files(
            field_name="payment_proof_files",
            allowed_extensions={"pdf"},
        )

    def clean(self):
        cleaned_data = super().clean()
        business_errors = _apply_invoice_business_rules(
            cleaned_data=cleaned_data,
            claim_date_received=getattr(self.instance.claim, "date_received", None) if getattr(self.instance, "claim_id", None) else None,
        )
        for field_name, message in business_errors.items():
            self.add_error(field_name, message)
        return cleaned_data


class VerificationInvoiceInlineEditForm(BaseStyledFormMixin, forms.ModelForm):
    class Meta:
        model = VerificationInvoice
        fields = [
            "goods_services",
            "transaction_type",
            "transaction_number",
            "transaction_date",
            "supplier_trn",
            "supplier_name",
            "plot_number",
            "tos",
            "total_amount_paid",
            "vat_approved",
            "vat_paid",
            "description",
        ]
        labels = {
            "goods_services": "Goods / Services",
            "transaction_type": "Transaction Type",
            "transaction_number": "Transaction #",
            "transaction_date": "Date",
            "supplier_trn": "Supplier TRN",
            "supplier_name": "Supplier Name",
            "plot_number": "Plot No.",
            "tos": "TOS",
            "total_amount_paid": "Total Paid",
            "vat_approved": "VAT Approved",
            "vat_paid": "VAT Paid",
            "description": "Description",
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        max_date_value = timezone.localdate().isoformat()
        self.fields["transaction_date"].widget = forms.DateInput(
            attrs={"type": "date", "class": "form-control", "max": max_date_value},
            format="%Y-%m-%d",
        )
        self.fields["transaction_date"].input_formats = ["%Y-%m-%d"]
        self.fields["supplier_trn"].widget.attrs.update(
            {
                "inputmode": "numeric",
                "data-trn-warning": "true",
                "autocomplete": "off",
                "spellcheck": "false",
            }
        )
        self.fields["goods_services"].widget.attrs.update({"autocomplete": "off"})
        self.fields["supplier_name"].widget.attrs.update({"autocomplete": "off", "spellcheck": "false"})
        self.fields["plot_number"].widget.attrs.update({"autocomplete": "off", "spellcheck": "false"})
        self.fields["tos"].widget.attrs.update({"data-invoice-tos": "true"})
        self.fields["description"].widget = forms.TextInput(
            attrs={
                "class": "form-control",
                "data-description-input": "true",
                "list": "verification-description-options",
                "autocomplete": "off",
                "spellcheck": "false",
            }
        )
        self.fields["vat_approved"].widget.attrs["class"] = (
            f"{self.fields['vat_approved'].widget.attrs.get('class', '').strip()} vat-approved-field".strip()
        )
        for decimal_field in ("total_amount_paid", "vat_approved", "vat_paid"):
            self.fields[decimal_field].widget.attrs.update({"step": "0.01", "min": "0"})
        for field in self.fields.values():
            if field.required:
                field.error_messages["required"] = REQUIRED_FIELD_MESSAGE

    def clean(self):
        cleaned_data = super().clean()
        business_errors = _apply_invoice_business_rules(
            cleaned_data=cleaned_data,
            claim_date_received=getattr(self.instance.claim, "date_received", None),
        )
        for field_name, message in business_errors.items():
            self.add_error(field_name, message)
        return cleaned_data


class DraftVerificationInvoiceForm(VerificationInvoiceForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field_name, field in self.fields.items():
            if field_name in {"invoice_files", "payment_proof_files"}:
                continue
            field.required = False
            field.widget.attrs.pop("required", None)


class BaseVerificationInvoiceFormSet(BaseInlineFormSet):
    def clean(self):
        super().clean()

        active_forms = 0
        claim_date_received = None
        if getattr(self, "data", None):
            claim_date_received = parse_date((self.data.get("date_received") or "").strip()) if self.data.get("date_received") else None
        if claim_date_received is None and getattr(self.instance, "date_received", None):
            claim_date_received = self.instance.date_received

        for form in self.forms:
            if not hasattr(form, "cleaned_data"):
                continue
            if form.cleaned_data.get("DELETE"):
                continue
            if not form.cleaned_data:
                continue

            active_forms += 1
            business_errors = _apply_invoice_business_rules(
                cleaned_data=form.cleaned_data,
                claim_date_received=claim_date_received,
            )
            for field_name, message in business_errors.items():
                form.add_error(field_name, message)

        if active_forms == 0:
            raise forms.ValidationError("At least one invoice row is required to create the verification report.")


class DraftVerificationInvoiceFormSetBase(BaseInlineFormSet):
    def clean(self):
        super().clean()

        for form in self.forms:
            if not hasattr(form, "cleaned_data"):
                continue
            if form.cleaned_data.get("DELETE"):
                continue
            if not form.cleaned_data:
                continue


VerificationInvoiceFormSet = inlineformset_factory(
    VerificationClaim,
    VerificationInvoice,
    form=VerificationInvoiceForm,
    formset=BaseVerificationInvoiceFormSet,
    extra=1,
    can_delete=True,
    min_num=0,
    validate_min=False,
)


DraftVerificationInvoiceFormSet = inlineformset_factory(
    VerificationClaim,
    VerificationInvoice,
    form=DraftVerificationInvoiceForm,
    formset=DraftVerificationInvoiceFormSetBase,
    extra=0,
    can_delete=True,
    min_num=0,
    validate_min=False,
)


class VerificationInvoiceDocumentUploadForm(forms.Form):
    category = forms.ChoiceField(choices=VerificationInvoiceDocument.Category.choices)
    files = MultipleFileField(
        widget=MultipleFileInput(
            attrs={
                "class": "form-control",
                "multiple": True,
                "data-max-file-bytes": str(MAX_DOCUMENT_UPLOAD_BYTES),
                "data-max-file-message": "Each file must be 2 MB or smaller.",
            }
        )
    )

    allowed_extensions = {
        VerificationInvoiceDocument.Category.INVOICE: {"pdf"},
        VerificationInvoiceDocument.Category.PAYMENT_PROOF: {"pdf"},
    }
    max_file_size = MAX_DOCUMENT_UPLOAD_BYTES

    def clean_files(self):
        files = self.cleaned_data.get("files") or []
        category = self.cleaned_data.get("category") or self.data.get("category")
        allowed = self.allowed_extensions.get(category, {"pdf"})
        normalized_files = []

        for uploaded_file in files:
            extension = Path(uploaded_file.name).suffix.lower().lstrip(".")
            if extension not in allowed:
                allowed_text = ", ".join(sorted(f".{ext}" for ext in allowed))
                raise forms.ValidationError(f"Allowed file types for this upload are: {allowed_text}.")
            if uploaded_file.size > self.max_file_size:
                raise forms.ValidationError("Each file must be 2 MB or smaller.")
            normalized_file = normalize_uploaded_file_to_pdf(uploaded_file)
            _validate_normalized_pdf_size(normalized_file, message="Each file must be 2 MB or smaller.")
            normalized_files.append(normalized_file)

        return normalized_files


class VerificationInvoiceDocumentBundleUploadForm(forms.Form):
    invoice_files = MultipleFileField(
        required=False,
        widget=MultipleFileInput(
            attrs={
                "class": "upload-dropzone__input",
                "multiple": True,
                "accept": ".pdf",
                "data-max-file-bytes": str(MAX_DOCUMENT_UPLOAD_BYTES),
                "data-max-file-message": "Each invoice file must be 2 MB or smaller.",
            }
        ),
    )
    payment_proof_files = MultipleFileField(
        required=False,
        widget=MultipleFileInput(
            attrs={
                "class": "upload-dropzone__input",
                "multiple": True,
                "accept": ".pdf",
                "data-max-file-bytes": str(MAX_DOCUMENT_UPLOAD_BYTES),
                "data-max-file-message": "Each payment proof file must be 2 MB or smaller.",
            }
        ),
    )

    max_file_size = MAX_DOCUMENT_UPLOAD_BYTES

    def clean(self):
        cleaned_data = super().clean()
        invoice_files = cleaned_data.get("invoice_files") or []
        payment_proof_files = cleaned_data.get("payment_proof_files") or []

        if not invoice_files and not payment_proof_files:
            raise forms.ValidationError("Select at least one invoice file or payment proof to upload.")

        return cleaned_data


class VerificationDescriptionOptionForm(BaseStyledFormMixin, forms.ModelForm):
    class Meta:
        model = VerificationDescriptionOption
        fields = ["label", "sort_order", "is_active"]
        labels = {
            "label": "Reason",
            "sort_order": "Order",
            "is_active": "Active",
        }

    def clean_invoice_files(self):
        files = self.cleaned_data.get("invoice_files") or []
        normalized_files = []
        for uploaded_file in files:
            extension = Path(uploaded_file.name).suffix.lower().lstrip(".")
            if extension != "pdf":
                raise forms.ValidationError("Invoice files must be PDF files.")
            if uploaded_file.size > self.max_file_size:
                raise forms.ValidationError("Each invoice file must be 2 MB or smaller.")
            normalized_file = normalize_uploaded_file_to_pdf(uploaded_file)
            _validate_normalized_pdf_size(
                normalized_file,
                message="Each invoice file must be 2 MB or smaller.",
            )
            normalized_files.append(normalized_file)
        return normalized_files

    def clean_payment_proof_files(self):
        files = self.cleaned_data.get("payment_proof_files") or []
        normalized_files = []
        for uploaded_file in files:
            extension = Path(uploaded_file.name).suffix.lower().lstrip(".")
            if extension != "pdf":
                raise forms.ValidationError("Payment proofs must be PDF files.")
            if uploaded_file.size > self.max_file_size:
                raise forms.ValidationError("Each payment proof file must be 2 MB or smaller.")
            normalized_file = normalize_uploaded_file_to_pdf(uploaded_file)
            _validate_normalized_pdf_size(
                normalized_file,
                message="Each payment proof file must be 2 MB or smaller.",
            )
            normalized_files.append(normalized_file)
        return normalized_files
