from decimal import Decimal

from rest_framework import serializers

from apps.core.tenancy import get_active_organization, validate_same_organization_field

from .models import Requisition, RequisitionQuote, Approval, RFQRFP, PurchaseOrder, ProcurementContract, GoodsReceipt, Invoice, Payment


def _same_org(serializer, value):
    """Field-level tenant check for a writable FK on a procurement serializer.

    The viewsets validate related objects in `perform_create` only, so before
    this a PATCH could repoint `vendor`, `requisition`, `purchase_order`,
    `goods_receipt` or `invoice` at another tenant's row (SEC-09). Declaring
    the check on the serializer runs it on create and update alike.
    """
    return validate_same_organization_field(serializer, value)


def _validate_budget_item(serializer, value):
    """BudgetItem reaches its organization through `category`, so it needs its
    own check rather than `validate_same_organization_field`."""
    if value is None:
        return value
    request = serializer.context.get("request")
    assert request is not None, (
        f"{serializer.__class__.__name__} must receive request in its context "
        "for cross-organization budget validation to run."
    )
    if value.category.organization_id != get_active_organization(request).id:
        raise serializers.ValidationError(
            f'Invalid pk "{value.pk}" - object does not exist.',
            code="does_not_exist",
        )
    return value


class RequisitionSerializer(serializers.ModelSerializer):
    """Serializer for Requisition model."""
    requested_by_name = serializers.SerializerMethodField()
    vendor_name = serializers.SerializerMethodField()
    budget_item_name = serializers.SerializerMethodField()
    budget_context = serializers.SerializerMethodField()

    class Meta:
        model = Requisition
        fields = [
            "id", "number", "title", "description", "status", "requested_by", "requested_by_name",
            "requesting_department", "project", "procurement_method", "exception_justification",
            "vendor", "vendor_name", "items", "total_amount",
            "budget_item", "budget_item_name", "budget_context", "notes",
            "created_at", "updated_at",
        ]
        read_only_fields = ["id", "number", "status", "requested_by", "created_at", "updated_at"]

    def validate_vendor(self, value):
        return _same_org(self, value)

    def validate_budget_item(self, value):
        return _validate_budget_item(self, value)

    def validate_project(self, value):
        return _same_org(self, value)

    def validate_requesting_department(self, value):
        return _same_org(self, value)

    def get_requested_by_name(self, obj):
        return obj.requested_by.get_full_name() or obj.requested_by.username

    def get_vendor_name(self, obj):
        return obj.vendor.name if obj.vendor_id else None

    def get_budget_item_name(self, obj):
        return obj.budget_item.name if obj.budget_item_id else None

    def get_budget_context(self, obj):
        # On list, the ViewSet precomputes committed totals for the whole page
        # in one query (see RequisitionViewSet.list) to avoid a query per row.
        # obj.budget_context() (one query) is only used as a fallback for
        # contexts that don't populate it, e.g. detail/retrieve.
        committed_by_item = self.context.get("committed_by_budget_item")
        if committed_by_item is not None:
            if not obj.budget_item_id:
                return {
                    "budget_item": None,
                    "budget_item_name": None,
                    "committed": None,
                    "warning": "No budget line linked — spend is untracked.",
                }
            return {
                "budget_item": str(obj.budget_item_id),
                "budget_item_name": obj.budget_item.name,
                "committed": committed_by_item.get(obj.budget_item_id, Decimal("0")),
                "warning": None,
            }
        return obj.budget_context()


class ApprovalSerializer(serializers.ModelSerializer):
    """Serializer for Approval model."""
    approver_name = serializers.SerializerMethodField()
    requisition_title = serializers.SerializerMethodField()
    requisition_number = serializers.SerializerMethodField()
    vendor_name = serializers.SerializerMethodField()

    class Meta:
        model = Approval
        fields = [
            "id", "requisition", "requisition_number", "requisition_title", "vendor_name",
            "level", "required_role", "approver", "approver_name", "status", "comment",
            "resolved_at", "created_at",
        ]
        read_only_fields = [
            "id", "requisition", "level", "required_role", "status",
            "approver", "resolved_at", "created_at",
        ]

    def get_approver_name(self, obj):
        if not obj.approver_id:
            return None
        return obj.approver.get_full_name() or obj.approver.username

    def get_requisition_title(self, obj):
        return obj.requisition.title if obj.requisition_id else None

    def get_requisition_number(self, obj):
        return obj.requisition.number if obj.requisition_id else None

    def get_vendor_name(self, obj):
        req = obj.requisition
        return req.vendor.name if req and req.vendor_id else None


class RequisitionQuoteSerializer(serializers.ModelSerializer):
    """Serializer for vendor quotes on a requisition."""
    vendor_name = serializers.SerializerMethodField()
    requisition_number = serializers.SerializerMethodField()
    awarded_by_name = serializers.SerializerMethodField()
    is_lowest = serializers.BooleanField(read_only=True)

    class Meta:
        model = RequisitionQuote
        fields = [
            "id", "requisition", "requisition_number", "vendor", "vendor_name",
            "quoted_amount", "delivery_estimate", "is_selected", "is_lowest",
            "award_reason", "awarded_by", "awarded_by_name", "awarded_at",
            "notes", "attachment", "created_at",
        ]
        # Award fields move only through the award action, not direct writes.
        read_only_fields = ["id", "created_at", "is_selected", "award_reason", "awarded_by", "awarded_at"]

    def validate_vendor(self, value):
        return _same_org(self, value)

    def validate_requisition(self, value):
        return _same_org(self, value)

    def get_vendor_name(self, obj):
        return obj.vendor.name if obj.vendor_id else None

    def get_requisition_number(self, obj):
        return obj.requisition.number if obj.requisition_id else None

    def get_awarded_by_name(self, obj):
        if not obj.awarded_by_id:
            return None
        return obj.awarded_by.get_full_name() or obj.awarded_by.username


class RFQRFPSerializer(serializers.ModelSerializer):
    """Serializer for RFQRFP model."""
    created_by_name = serializers.SerializerMethodField()
    vendor_name = serializers.SerializerMethodField()

    class Meta:
        model = RFQRFP
        fields = [
            "id", "title", "rfq_rfp_type", "description", "requirements", "deadline",
            "status", "created_by", "created_by_name", "vendor", "vendor_name",
            "attachments", "created_at", "updated_at"
        ]
        read_only_fields = ["id", "created_by", "created_at", "updated_at"]

    def validate_vendor(self, value):
        return _same_org(self, value)

    def get_created_by_name(self, obj):
        return obj.created_by.get_full_name() or obj.created_by.username

    def get_vendor_name(self, obj):
        if obj.vendor:
            return obj.vendor.name
        return None


class PurchaseOrderSerializer(serializers.ModelSerializer):
    """Serializer for PurchaseOrder model."""
    vendor_name = serializers.SerializerMethodField()
    vendor_address = serializers.SerializerMethodField()
    vendor_contact_person = serializers.SerializerMethodField()
    vendor_email = serializers.SerializerMethodField()
    vendor_phone = serializers.SerializerMethodField()
    vendor_tax_id = serializers.SerializerMethodField()
    requisition_number = serializers.SerializerMethodField()
    requisition_title = serializers.SerializerMethodField()

    class Meta:
        model = PurchaseOrder
        fields = [
            "id", "order_number", "vendor", "vendor_name",
            "vendor_address", "vendor_contact_person", "vendor_email", "vendor_phone", "vendor_tax_id",
            "requisition", "requisition_number", "requisition_title",
            "created_by", "issued_by", "issued_at", "currency",
            "items", "subtotal", "tax", "total", "status", "expected_delivery",
            "notes", "created_at", "updated_at"
        ]
        read_only_fields = ["id", "status", "created_by", "issued_by", "issued_at", "created_at", "updated_at"]

    def validate_vendor(self, value):
        return _same_org(self, value)

    def validate_requisition(self, value):
        return _same_org(self, value)

    def get_vendor_name(self, obj):
        return obj.vendor.name if obj.vendor_id else None

    def get_vendor_address(self, obj):
        return obj.vendor.address if obj.vendor_id else None

    def get_vendor_contact_person(self, obj):
        return obj.vendor.contact_person if obj.vendor_id else None

    def get_vendor_email(self, obj):
        return obj.vendor.email if obj.vendor_id else None

    def get_vendor_phone(self, obj):
        return obj.vendor.phone if obj.vendor_id else None

    def get_vendor_tax_id(self, obj):
        return obj.vendor.tax_id if obj.vendor_id else None

    def get_requisition_number(self, obj):
        return obj.requisition.number if obj.requisition_id else None

    def get_requisition_title(self, obj):
        return obj.requisition.title if obj.requisition_id else None


class ProcurementContractSerializer(serializers.ModelSerializer):
    """Serializer for ProcurementContract model."""
    vendor_name = serializers.SerializerMethodField()

    class Meta:
        model = ProcurementContract
        fields = [
            "id", "title", "contract_number", "vendor", "vendor_name", "requisition", "created_by", "signed_by", "signed_at", "currency", "value",
            "start_date", "end_date", "status", "document", "terms", "created_at", "updated_at"
        ]
        read_only_fields = ["id", "status", "created_by", "signed_by", "signed_at", "created_at", "updated_at"]

    def validate_vendor(self, value):
        return _same_org(self, value)

    def validate_requisition(self, value):
        return _same_org(self, value)

    def get_vendor_name(self, obj):
        return obj.vendor.name if obj.vendor_id else None


class GoodsReceiptSerializer(serializers.ModelSerializer):
    """Serializer for GoodsReceipt model."""
    purchase_order_number = serializers.SerializerMethodField()
    vendor_name = serializers.SerializerMethodField()
    received_by_name = serializers.SerializerMethodField()

    class Meta:
        model = GoodsReceipt
        fields = [
            "id", "purchase_order", "purchase_order_number", "vendor_name", "receipt_number",
            "received_date", "items", "status", "notes", "received_by", "received_by_name", "created_at"
        ]
        read_only_fields = ["id", "status", "received_by", "created_at"]

    def validate_purchase_order(self, value):
        return _same_org(self, value)

    def get_purchase_order_number(self, obj):
        return obj.purchase_order.order_number if obj.purchase_order_id else None

    def get_vendor_name(self, obj):
        po = obj.purchase_order
        return po.vendor.name if po and po.vendor_id else None

    def get_received_by_name(self, obj):
        if obj.received_by:
            return obj.received_by.get_full_name() or obj.received_by.username
        return None


class InvoiceSerializer(serializers.ModelSerializer):
    """Serializer for Invoice model."""
    vendor_name = serializers.SerializerMethodField()
    purchase_order_number = serializers.SerializerMethodField()

    match_detail = serializers.SerializerMethodField()

    class Meta:
        model = Invoice
        fields = [
            "id", "invoice_number", "vendor", "vendor_name",
            "purchase_order", "purchase_order_number",
            "goods_receipt", "items", "amount", "tax", "total", "due_date", "status",
            "match_status", "match_detail",
            "created_by", "approved_by", "approved_at", "paid_at", "notes", "created_at"
        ]
        read_only_fields = ["id", "status", "created_by", "approved_by", "approved_at", "paid_at", "created_at", "match_status"]

    def validate_vendor(self, value):
        return _same_org(self, value)

    def validate_purchase_order(self, value):
        return _same_org(self, value)

    def validate_goods_receipt(self, value):
        return _same_org(self, value)

    def get_vendor_name(self, obj):
        return obj.vendor.name if obj.vendor_id else None

    def get_purchase_order_number(self, obj):
        return obj.purchase_order.order_number if obj.purchase_order_id else None

    def get_match_detail(self, obj):
        return obj.match_detail()


class PaymentSerializer(serializers.ModelSerializer):
    """Serializer for Payment model."""
    invoice_number = serializers.SerializerMethodField()
    vendor_name = serializers.SerializerMethodField()
    invoice_total = serializers.SerializerMethodField()

    class Meta:
        model = Payment
        fields = [
            "id", "invoice", "invoice_number", "vendor_name", "invoice_total",
            "amount", "method", "reference",
            "status", "prepared_by", "fund_approved_by", "fund_approved_at", "fund_approval_comment", "released_by", "paid_at", "notes", "created_at"
        ]
        read_only_fields = ["id", "status", "prepared_by", "fund_approved_by", "fund_approved_at", "fund_approval_comment", "released_by", "paid_at", "created_at"]

    def validate_invoice(self, value):
        return _same_org(self, value)

    def get_invoice_number(self, obj):
        return obj.invoice.invoice_number if obj.invoice_id else None

    def get_vendor_name(self, obj):
        inv = obj.invoice
        return inv.vendor.name if inv and inv.vendor_id else None

    def get_invoice_total(self, obj):
        return obj.invoice.total if obj.invoice_id else None

    def validate(self, attrs):
        # 3-way match gate: an invoice must pass PO=GRN=Invoice before it can
        # be paid. Only enforced on create (the initial payment record); status
        # transitions on an existing payment are left alone.
        invoice = attrs.get("invoice") or getattr(self.instance, "invoice", None)
        if self.instance is None and invoice and (invoice.status != "approved" or invoice.match_detail()["status"] != "matched"):
            raise serializers.ValidationError(
                {"invoice": f"Invoice failed 3-way match (status: {invoice.match_status}). "
                            "Resolve the PO/GRN/Invoice mismatch before paying."}
            )
        return attrs
