"""Supplier portal views — scoped to the authenticated supplier."""

from __future__ import annotations

import logging

from django.utils import timezone
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.parsers import JSONParser, MultiPartParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSet

from apps.core.pagination import StandardPagination
from apps.suppliers.client import get_client
from apps.suppliers.models import SupplierProfile, SupplierTransaction
from apps.suppliers.permissions import IsAdminRole, IsLinkedSupplier
from apps.suppliers.serializers import (
    LinkSupplierSerializer,
    RefundStatusSerializer,
    SendPaymentSerializer,
    UploadDocumentSerializer,
    map_payment_status,
)

logger = logging.getLogger(__name__)


def _supplier_code(request) -> int:
    """The authenticated user's MyFatoorah SupplierCode.

    `IsLinkedSupplier` guarantees `request.user.supplier_profile` exists.
    """
    return request.user.supplier_profile.supplier_code


class MeViewSet(ViewSet):
    """Endpoints scoped to the authenticated supplier."""

    permission_classes = [IsAuthenticated, IsLinkedSupplier]
    parser_classes = [JSONParser, MultiPartParser]
    pagination_class = StandardPagination

    _paginator = None

    @property
    def paginator(self):
        if self._paginator is None:
            self._paginator = self.pagination_class()
        return self._paginator

    def list(self, request):
        """GET /me/ — full supplier profile (GetSupplierDetails)."""
        data = get_client().get_supplier(_supplier_code(request))
        return Response(data)

    @action(detail=False, methods=["get"], url_path="dashboard")
    def dashboard(self, request):
        """GET /me/dashboard/ — totals and balances."""
        data = get_client().get_supplier_dashboard(_supplier_code(request))
        return Response(data)

    @action(detail=False, methods=["get"], url_path="deposits")
    def deposits(self, request):
        """GET /me/deposits/ — deposit history."""
        params: dict = {}
        if request.query_params.get("search"):
            params["search"] = request.query_params["search"]
        if request.query_params.get("sort_column"):
            params["sort_column"] = request.query_params["sort_column"]
        if request.query_params.get("sort_direction"):
            params["sort_direction"] = request.query_params["sort_direction"]
        for k in ("start", "length"):
            raw = request.query_params.get(k)
            if raw is not None:
                try:
                    params[k] = int(raw)
                except ValueError:
                    pass
        data = get_client().get_supplier_deposits(
            _supplier_code(request), **params
        )
        return Response(data)

    @action(detail=False, methods=["get", "post"], url_path="documents")
    def documents(self, request):
        """GET /me/documents/ — list. POST /me/documents/ — upload."""
        client = get_client()
        code = _supplier_code(request)
        if request.method == "GET":
            return Response(client.get_supplier_documents(code))

        serializer = UploadDocumentSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        result = client.upload_supplier_document(serializer.to_payload(code))
        return Response(
            {"_message": "msg.documentUploaded", "result": result},
            status=status.HTTP_201_CREATED,
        )

    @action(detail=False, methods=["get"], url_path="deposits/invoices")
    def deposit_invoices(self, request):
        """GET /me/deposits/invoices/?deposit_reference=… — drill into a deposit."""
        deposit_reference = request.query_params.get("deposit_reference")
        if not deposit_reference:
            return Response(
                {"deposit_reference": "This query parameter is required."},
                status=status.HTTP_400_BAD_REQUEST,
            )
        data = get_client().get_deposited_invoices(
            deposit_reference,
            type_="Supplier",
            supplier_code=_supplier_code(request),
        )
        return Response(data)

    @action(detail=False, methods=["get"], url_path="refunds")
    def refunds(self, request):
        """GET /me/refunds/?key=&key_type= — refund status lookup."""
        serializer = RefundStatusSerializer(data=request.query_params)
        serializer.is_valid(raise_exception=True)
        data = get_client().get_refund_status(
            serializer.validated_data["key"],
            serializer.validated_data["key_type"],
        )
        return Response(data)

    @action(detail=False, methods=["get"], url_path="transactions")
    def transactions(self, request):
        """GET /me/transactions/ — supplier transaction list.

        We persist only the InvoiceId locally when an invoice is created
        (POST /me/payments/). This endpoint pages over those ids and fetches
        live details from MyFatoorah's GetPaymentStatus for each one, so the
        data (status, customer, value, payment method, card) is always fresh.

        Query params:
        - status: keep only rows whose live InvoiceStatus matches (e.g. Paid)
        - page / page_size: pagination over stored invoice ids
        """
        qs = request.user.supplier_profile.transactions.all()

        page = self.paginator.paginate_queryset(qs, request, view=self)
        client = get_client()

        rows = []
        for txn in page:
            try:
                data = client.get_payment_status(txn.invoice_id, key_type="InvoiceId")
            except Exception:
                logger.warning("Failed to fetch status for invoice %s", txn.invoice_id)
                rows.append({"invoice_id": txn.invoice_id, "status": "Unknown"})
                continue
            rows.append(map_payment_status(txn.invoice_id, data))

        status_filter = request.query_params.get("status")
        if status_filter:
            rows = [
                r for r in rows
                if str(r.get("status", "")).lower() == status_filter.lower()
            ]

        return self.paginator.get_paginated_response(rows)

    # ----------------------------- Payments -----------------------------

    @action(detail=False, methods=["post"], url_path="payments")
    def payments(self, request):
        """POST /me/payments/ — create a payment link (SendPayment).

        On success the invoice is recorded locally so it can later appear in
        GET /me/transactions/ (MyFatoorah has no list-invoices API).
        """
        serializer = SendPaymentSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        payload = serializer.to_payload(_supplier_code(request))
        data = get_client().send_payment(payload)

        self._record_transaction(request, data)

        return Response(
            {"_message": "msg.paymentLinkCreated", "result": data},
            status=status.HTTP_201_CREATED,
        )

    @staticmethod
    def _record_transaction(request, result) -> None:
        """Persist only the InvoiceId locally. Never blocks the response."""
        if not isinstance(result, dict):
            return
        invoice_id = result.get("InvoiceId") or result.get("InvoiceID")
        if not invoice_id:
            return
        try:
            SupplierTransaction.objects.get_or_create(
                invoice_id=str(invoice_id),
                defaults={"supplier": request.user.supplier_profile},
            )
        except Exception:
            logger.exception("Failed to record transaction for invoice %s", invoice_id)

    @action(
        detail=False,
        methods=["get"],
        url_path=r"payments/(?P<invoice_id>[^/.]+)",
    )
    def payment_status(self, request, invoice_id=None):
        """GET /me/payments/<invoice_id>/ — check payment status."""
        data = get_client().get_payment_status(invoice_id, key_type="InvoiceId")
        return Response(data)

    @action(detail=False, methods=["get"], url_path="payment-methods")
    def payment_methods(self, request):
        """GET /me/payment-methods/?amount=&currency= — list payment methods."""
        try:
            amount = float(request.query_params.get("amount", 1))
        except (TypeError, ValueError):
            amount = 1.0
        currency = request.query_params.get("currency", "AED")
        data = get_client().initiate_payment(amount, currency)
        return Response(data)


class AdminLinkSupplierView(APIView):
    """Link an existing portal user to an existing MyFatoorah supplier.

    Verifies the supplier exists upstream (GetSupplierDetails) before persisting.
    """

    permission_classes = [IsAuthenticated, IsAdminRole]

    def post(self, request):
        serializer = LinkSupplierSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        from django.contrib.auth import get_user_model

        User = get_user_model()
        try:
            user = User.objects.get(pk=serializer.validated_data["user_id"])
        except User.DoesNotExist:
            return Response(
                {"_message": "msg.userNotFound"},
                status=status.HTTP_404_NOT_FOUND,
            )

        code = serializer.validated_data["supplier_code"]
        # Raises MyFatoorahValidationError if the code is unknown upstream.
        get_client().get_supplier(code)

        profile, _ = SupplierProfile.objects.update_or_create(
            user=user,
            defaults={"supplier_code": code},
        )
        return Response(
            {
                "_message": "msg.supplierLinked",
                "user_id": user.id,
                "supplier_code": profile.supplier_code,
            },
            status=status.HTTP_201_CREATED,
        )


class PaymentWebhookView(APIView):
    """Public webhook endpoint for MyFatoorah payment notifications.

    MyFatoorah calls this when a payment status changes. On successful
    payment, we send an email to the supplier who owns the invoice.

    No JWT auth — this is called by MyFatoorah's servers.
    Signature is verified using the webhook secret key (v2 signing).
    """

    permission_classes = []
    authentication_classes = []

    def post(self, request):
        import base64
        import hashlib
        import hmac

        from django.conf import settings as django_settings

        from apps.suppliers.notifications import notify_supplier_payment_success

        # --- Verify webhook signature (v2) ---
        webhook_secret = (django_settings.MYFATOORAH or {}).get("WEBHOOK_SECRET", "")
        signature = request.headers.get("MyFatoorah-Signature", "")
        if webhook_secret:
            if not signature:
                logger.warning("Webhook rejected: no signature header present")
                return Response(
                    {"detail": "Missing signature"},
                    status=status.HTTP_403_FORBIDDEN,
                )

            # MyFatoorah v2 signature: HMAC-SHA256 of ordered data fields, base64 encoded.
            # For PAYMENT_STATUS_CHANGED the fields are:
            # Invoice.Id, Invoice.Status, Transaction.Status, Transaction.PaymentId,
            # Invoice.ExternalIdentifier
            data = request.data or {}
            invoice = data.get("Data", {}).get("Invoice", {}) if isinstance(data.get("Data"), dict) else {}
            transaction = data.get("Data", {}).get("Transaction", {}) if isinstance(data.get("Data"), dict) else {}

            sig_parts = [
                f"Invoice.Id={invoice.get('Id') or ''}",
                f"Invoice.Status={invoice.get('Status') or ''}",
                f"Transaction.Status={transaction.get('Status') or ''}",
                f"Transaction.PaymentId={transaction.get('PaymentId') or ''}",
                f"Invoice.ExternalIdentifier={invoice.get('ExternalIdentifier') or ''}",
            ]
            sig_string = ",".join(sig_parts)

            computed = base64.b64encode(
                hmac.new(
                    webhook_secret.encode("utf-8"),
                    sig_string.encode("utf-8"),
                    hashlib.sha256,
                ).digest()
            ).decode("ascii")

            if not hmac.compare_digest(signature, computed):
                logger.warning("Webhook rejected: signature mismatch")
                return Response(
                    {"detail": "Invalid signature"},
                    status=status.HTTP_403_FORBIDDEN,
                )

        # --- Process the webhook payload ---
        data = request.data or {}

        # Only handle PAYMENT_STATUS_CHANGED (event code 1). Ignore refunds,
        # balance transfers, supplier updates, recurring updates, disputes, etc.
        event = data.get("Event") or {}
        event_code = event.get("Code")
        if event_code is not None and event_code != 1:
            logger.info("Webhook ignored: event code %s is not PAYMENT_STATUS_CHANGED", event_code)
            return Response({"_message": "msg.webhookReceived"})

        webhook_data = data.get("Data") or data.get("data") or {}
        if not isinstance(webhook_data, dict):
            return Response({"_message": "msg.webhookReceived"})

        invoice = webhook_data.get("Invoice") or {}
        transaction = webhook_data.get("Transaction") or {}
        customer = webhook_data.get("Customer") or {}
        amount = webhook_data.get("Amount") or {}

        # The signature already proves authenticity, so we trust the payload
        # directly (no slow GetPaymentStatus round-trip). Only act on success.
        invoice_status = str(invoice.get("Status") or "")
        txn_status = str(transaction.get("Status") or "")
        if invoice_status.upper() != "PAID" and txn_status.upper() != "SUCCESS":
            return Response({"_message": "msg.webhookReceived"})

        invoice_id = invoice.get("Id")
        if not invoice_id:
            return Response(
                {"detail": "Missing invoice id"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        # Build a normalized dict for the email (matches notify helper shape).
        display_amount = ""
        if amount.get("ValueInDisplayCurrency") and amount.get("DisplayCurrency"):
            display_amount = (
                f"{amount['ValueInDisplayCurrency']} {amount['DisplayCurrency']}"
            )
        email_data = {
            "InvoiceId": invoice_id,
            "InvoiceReference": invoice.get("Reference", ""),
            "CustomerName": customer.get("Name", ""),
            "InvoiceDisplayValue": display_amount,
        }

        # Find the supplier(s) this invoice belongs to and notify each.
        suppliers = webhook_data.get("Suppliers") or []
        for sup in suppliers:
            supplier_code = sup.get("Code") or sup.get("SupplierCode")
            if not supplier_code:
                continue
            try:
                profile = SupplierProfile.objects.select_related("user").get(
                    supplier_code=supplier_code
                )
            except SupplierProfile.DoesNotExist:
                continue

            txn, _ = SupplierTransaction.objects.get_or_create(
                invoice_id=str(invoice_id),
                defaults={"supplier": profile},
            )

            # Idempotency: only notify once per invoice (handles duplicate webhooks).
            if txn.notified_at:
                logger.info("Skipping duplicate notification for invoice %s", invoice_id)
                continue

            notify_supplier_payment_success(profile.user, email_data)
            SupplierTransaction.objects.filter(pk=txn.pk).update(
                notified_at=timezone.now()
            )

        return Response({"_message": "msg.webhookReceived"})

