"""
Webhook domain: verify Shopify signatures, route to orders/products sync.
Move any existing webhook handling here.
"""
from datetime import date, timezone as dt_timezone
from decimal import Decimal, InvalidOperation
from typing import Any
import logging

from django.utils.timezone import is_naive

logger = logging.getLogger(__name__)


class WebhookService:
    """Business logic for incoming Shopify product webhooks."""

    @staticmethod
    def handle_product_updated(payload: dict[str, Any]) -> dict[str, Any]:
        """products/update or products/create → sync product to Digistore."""
        from products.services import ProductService
        product_id = str(payload.get("id", ""))
        if not product_id:
            return {"ok": False, "error": "missing_product_id"}
        return ProductService.sync_product(product_id)

    @staticmethod
    def handle_product_deleted(payload: dict[str, Any]) -> dict[str, Any]:
        """products/delete → delete product in Digistore and locally."""
        from products.models import Product
        from products.services import DigistoreService

        product_id = str(payload.get("id", ""))
        if not product_id:
            return {"ok": False, "error": "missing_product_id"}

        # Find local mapping
        p = Product.objects.filter(shopify_product_id=product_id).first()
        digistore_id = p.digistore_product_id if p else ""

        # Try to delete in Digistore24 if we have an id
        digistore_deleted = False
        digistore_error = None
        if digistore_id:
            try:
                DigistoreService.delete_product(digistore_id)
                digistore_deleted = True
            except Exception as e:
                digistore_error = str(e)

        # Delete local record
        deleted_count, _ = Product.objects.filter(shopify_product_id=product_id).delete()

        return {
            "ok": True,
            "shopify_product_id": product_id,
            "digistore_product_id": digistore_id or None,
            "digistore_deleted": digistore_deleted,
            "local_deleted": deleted_count,
            "digistore_error": digistore_error,
        }

    @staticmethod
    def _decimal(value: Any) -> Decimal:
        try:
            return Decimal(str(value or "0").strip().replace(",", "."))
        except (InvalidOperation, ValueError, TypeError, AttributeError):
            return Decimal("0")

    @staticmethod
    def _extract_refund_amount(payload: dict[str, Any]) -> Decimal:
        # Preferred: refund transactions with kind=refund.
        amount = Decimal("0")
        for tx in payload.get("transactions") or []:
            if not isinstance(tx, dict):
                continue
            kind = str(tx.get("kind") or "").strip().lower()
            if kind and kind != "refund":
                continue
            amount += WebhookService._decimal(tx.get("amount"))

        if amount > 0:
            return amount

        # Fallback: line-level subtotal + tax.
        for row in payload.get("refund_line_items") or []:
            if not isinstance(row, dict):
                continue
            amount += WebhookService._decimal(row.get("subtotal"))
            amount += WebhookService._decimal(row.get("total_tax"))

        return amount

    @staticmethod
    def _is_digistore_refund_successful(api_result: dict[str, Any]) -> tuple[bool, str]:
        """
        Digistore can return top-level `result=success` even when refund details
        contain refused rows (for example refund window expired). Treat those as
        failures so we do not mark local events processed incorrectly.
        """
        if not isinstance(api_result, dict):
            return False, "invalid_response"

        ds_result = str(api_result.get("result") or "").strip().lower()
        ds_data = (
            api_result.get("data")
            if isinstance(api_result.get("data"), dict)
            else {}
        )
        ds_status = str(ds_data.get("status") or "").strip().lower()

        if ds_result != "success":
            return False, f"result={ds_result or 'missing'}"
        if ds_status and ds_status != "success":
            return False, f"status={ds_status}"

        details = ds_data.get("details")
        if isinstance(details, list):
            refused_messages: list[str] = []
            detail_statuses = [
                str(row.get("status") or "").strip().lower()
                for row in details
                if isinstance(row, dict)
            ]
            if detail_statuses and all(s == "refused" for s in detail_statuses):
                codes = [
                    str(row.get("code") or "").strip()
                    for row in details
                    if isinstance(row, dict)
                ]
                for row in details:
                    if not isinstance(row, dict):
                        continue
                    code = str(row.get("code") or "").strip()
                    msg = str(
                        row.get("result_msg")
                        or row.get("message")
                        or ""
                    ).strip()
                    parts = [p for p in (code, msg) if p]
                    if parts:
                        refused_messages.append(" | ".join(parts))
                refused_reason = "; ".join(refused_messages)
                if refused_reason:
                    return False, f"details_refused:{refused_reason}"
                refused_code = next((c for c in codes if c), "refused")
                return False, f"details_refused:{refused_code}"

        refund_amount = WebhookService._decimal(ds_data.get("refund_amount"))
        if details and refund_amount <= 0:
            return False, "refund_amount_zero"

        return True, ""

    @staticmethod
    def _should_retry_with_force(ds_reason: str) -> bool:
        reason = str(ds_reason or "").strip().lower()
        return "refund_right_expired" in reason

    @staticmethod
    def _save_refund_event(event, **fields) -> None:
        for k, v in fields.items():
            setattr(event, k, v)
        event.save(update_fields=[*fields.keys(), "updated_at"])

    @staticmethod
    def _call_digistore_refund(
        *,
        refund_type: str,
        digistore_order_id: str,
        refund_amount: Decimal,
        reason: str,
        force: bool = False,
    ) -> dict[str, Any]:
        from orders.services import OrderService

        if refund_type == "full":
            result = OrderService.refund_purchase(
                digistore_order_id,
                reason=reason,
                force=force,
            )
            logger.info(
                "Digistore full refund%s: %s",
                " (force)" if force else "",
                result,
            )
            return result

        result = OrderService.refund_partially(
            digistore_order_id,
            float(refund_amount),
            reason=reason,
            force=force,
        )
        logger.info(
            "Digistore partial refund%s: %s",
            " (force)" if force else "",
            result,
        )
        return result


    @staticmethod
    def handle_refund_created(payload: dict[str, Any], *, webhook_id: str = "") -> dict[str, Any]:
        from orders.models import Order
        from webhooks.models import ShopifyRefundEvent

        event_id = (webhook_id or "").strip() or str(payload.get("id") or "").strip()
        if not event_id:
            return {"ok": False, "error": "missing_webhook_id"}

        event, created = ShopifyRefundEvent.objects.get_or_create(
            webhook_id=event_id,
            defaults={"topic": "refunds/create", "payload": payload},
        )

        if not created and event.processed:
            return {"ok": True, "duplicate": True, "webhook_id": event_id}

        shopify_order_id = str(payload.get("order_id") or "").strip()
        if not shopify_order_id:
            WebhookService._save_refund_event(
                event,
                payload=payload,
                error="missing_shopify_order_id",
            )
            return {"ok": False, "error": "missing_shopify_order_id"}

        order = (
            Order.objects.filter(shopify_order_id=shopify_order_id)
            .order_by("pk")
            .first()
        )

        if not order or not order.digistore_order_id:
            WebhookService._save_refund_event(
                event,
                shopify_order_id=shopify_order_id,
                payload=payload,
                error="order_mapping_not_found",
            )
            return {"ok": False, "error": "order_mapping_not_found"}

        refund_amount = WebhookService._extract_refund_amount(payload)
        if refund_amount <= 0:
            WebhookService._save_refund_event(
                event,
                shopify_order_id=shopify_order_id,
                digistore_order_id=order.digistore_order_id,
                payload=payload,
                error="refund_amount_not_detected",
            )
            return {"ok": False, "error": "refund_amount_not_detected"}

        # Determine type
        raw_order_total = (
            order.raw_payload.get("amount_brutto")
            if isinstance(order.raw_payload, dict)
            else None
        ) or (
            order.raw_payload.get("amount")
            if isinstance(order.raw_payload, dict)
            else None
        )

        order_total = WebhookService._decimal(raw_order_total)
        is_shopify_full_refund = order_total > 0 and refund_amount >= order_total
        refund_type = "full" if is_shopify_full_refund else "partial"

        reason = str(payload.get("note") or "Shopify refund webhook").strip()[:255]

        # -------------------------
        # DIGISTORE CALL
        # -------------------------
        try:
            api_result = WebhookService._call_digistore_refund(
                refund_type=refund_type,
                digistore_order_id=order.digistore_order_id,
                refund_amount=refund_amount,
                reason=reason,
                force=False,
            )
        except Exception as exc:
            WebhookService._save_refund_event(
                event,
                shopify_order_id=shopify_order_id,
                digistore_order_id=order.digistore_order_id,
                refund_amount=refund_amount,
                refund_type=refund_type,
                payload=payload,
                processed=False,
                error=f"digistore_exception: {exc}",
            )
            return {"ok": False, "error": "digistore_exception"}

        # -------------------------
        # VALIDATE RESPONSE (FIXED)
        # -------------------------
        if not isinstance(api_result, dict):
            WebhookService._save_refund_event(
                event,
                shopify_order_id=shopify_order_id,
                digistore_order_id=order.digistore_order_id,
                refund_amount=refund_amount,
                refund_type=refund_type,
                payload=payload,
                processed=False,
                error="invalid_digistore_response",
            )
            return {"ok": False, "error": "invalid_digistore_response"}

        is_success, ds_reason = WebhookService._is_digistore_refund_successful(api_result)
        # Retry with `force=true` when Digistore refused because refund window expired.
        if (
            not is_success
            and WebhookService._should_retry_with_force(ds_reason)
        ):
            try:
                api_result = WebhookService._call_digistore_refund(
                    refund_type=refund_type,
                    digistore_order_id=order.digistore_order_id,
                    refund_amount=refund_amount,
                    reason=reason,
                    force=True,
                )
                is_success, ds_reason = WebhookService._is_digistore_refund_successful(
                    api_result
                )
            except Exception as exc:
                WebhookService._save_refund_event(
                    event,
                    shopify_order_id=shopify_order_id,
                    digistore_order_id=order.digistore_order_id,
                    refund_amount=refund_amount,
                    refund_type=refund_type,
                    payload=payload,
                    processed=False,
                    error=f"digistore_exception_force_retry: {exc}",
                )
                return {"ok": False, "error": "digistore_exception_force_retry"}

        if not is_success:
            WebhookService._save_refund_event(
                event,
                shopify_order_id=shopify_order_id,
                digistore_order_id=order.digistore_order_id,
                refund_amount=refund_amount,
                refund_type=refund_type,
                payload=payload,
                processed=False,
                error=f"digistore_failed: {ds_reason}",
            )
            return {"ok": False, "error": "digistore_failed", "reason": ds_reason}

        # -------------------------
        # SUCCESS
        # -------------------------
        order.status = "refunded" if refund_type == "full" else "partially refunded"
        order.save(update_fields=["status", "updated_at"])

        WebhookService._save_refund_event(
            event,
            shopify_order_id=shopify_order_id,
            digistore_order_id=order.digistore_order_id,
            refund_amount=refund_amount,
            refund_type=refund_type,
            payload=payload,
            processed=True,
            error="",
        )

        return {"ok": True}

    # =====================================================================
    # FULFILLMENT TRACKING (fulfillments/create | fulfillments/update)
    # =====================================================================

    @staticmethod
    def _save_fulfillment_event(event, **fields) -> None:
        for k, v in fields.items():
            setattr(event, k, v)
        event.save(update_fields=[*fields.keys(), "updated_at"])

    @staticmethod
    def _extract_fulfillment_tracking(payload: dict[str, Any]) -> dict[str, str]:
        """Pull tracking number/company/url from a Shopify fulfillment payload."""
        number = str(payload.get("tracking_number") or "").strip()
        if not number:
            numbers = payload.get("tracking_numbers") or []
            if isinstance(numbers, list):
                for n in numbers:
                    if str(n or "").strip():
                        number = str(n).strip()
                        break

        url = str(payload.get("tracking_url") or "").strip()
        if not url:
            urls = payload.get("tracking_urls") or []
            if isinstance(urls, list):
                for u in urls:
                    if str(u or "").strip():
                        url = str(u).strip()
                        break

        company = str(payload.get("tracking_company") or "").strip()
        return {"number": number, "company": company, "url": url}

    @staticmethod
    def _tracking_sync_cutoff_date() -> date | None:
        from django.conf import settings

        raw = str(getattr(settings, "DIGISTORE_TRACKING_SYNC_AFTER", "") or "").strip()
        if not raw:
            return None
        try:
            return date.fromisoformat(raw[:10])
        except ValueError:
            logger.warning("Invalid DIGISTORE_TRACKING_SYNC_AFTER=%r; ignoring cutoff", raw)
            return None

    @staticmethod
    def _is_historical_fulfillment_sync(order) -> bool:
        """True when this webhook belongs to an order created before the cutoff."""
        cutoff = WebhookService._tracking_sync_cutoff_date()
        if cutoff is None:
            return False
        created = getattr(order, "created_at", None)
        if created is None:
            return False
        if is_naive(created):
            created_utc = created.replace(tzinfo=dt_timezone.utc)
        else:
            created_utc = created.astimezone(dt_timezone.utc)
        return created_utc.date() < cutoff

    @staticmethod
    def handle_fulfillment_created_or_updated(
        payload: dict[str, Any],
        *,
        webhook_id: str = "",
        topic: str = "fulfillments/create",
    ) -> dict[str, Any]:
        """fulfillments/create|update → push Shopify tracking to Digistore updateDelivery."""
        from django.conf import settings

        from orders.models import Order
        from orders.services import OrderService
        from webhooks.models import ShopifyFulfillmentEvent

        event_id = (webhook_id or "").strip() or str(payload.get("id") or "").strip()
        if not event_id:
            return {"ok": False, "error": "missing_webhook_id"}

        shopify_order_id = str(payload.get("order_id") or "").strip()
        order = None
        if shopify_order_id:
            order = (
                Order.objects.filter(shopify_order_id=shopify_order_id)
                .order_by("pk")
                .first()
            )
            if order and order.digistore_order_id:
                if WebhookService._is_historical_fulfillment_sync(order):
                    ShopifyFulfillmentEvent.objects.filter(
                        webhook_id=event_id
                    ).delete()
                    return {
                        "ok": True,
                        "skipped": True,
                        "reason": "historical_order",
                    }

        event, created = ShopifyFulfillmentEvent.objects.get_or_create(
            webhook_id=event_id,
            defaults={"topic": topic, "payload": payload},
        )

        if not created and event.processed:
            return {"ok": True, "duplicate": True, "webhook_id": event_id}

        if not shopify_order_id:
            WebhookService._save_fulfillment_event(
                event,
                topic=topic,
                payload=payload,
                error="missing_shopify_order_id",
            )
            return {"ok": False, "error": "missing_shopify_order_id"}

        if not order or not order.digistore_order_id:
            WebhookService._save_fulfillment_event(
                event,
                topic=topic,
                shopify_order_id=shopify_order_id,
                payload=payload,
                error="order_mapping_not_found",
            )
            return {"ok": False, "error": "order_mapping_not_found"}

        tracking = WebhookService._extract_fulfillment_tracking(payload)
        if not tracking["number"]:
            WebhookService._save_fulfillment_event(
                event,
                topic=topic,
                shopify_order_id=shopify_order_id,
                digistore_order_id=order.digistore_order_id,
                payload=payload,
                error="no_tracking_number",
            )
            return {"ok": False, "error": "no_tracking_number"}

        notify = bool(
            getattr(settings, "DIGISTORE_TRACKING_NOTIFY_VIA_EMAIL", False)
        )

        try:
            result = OrderService.sync_tracking_to_digistore(
                digistore_order_id=order.digistore_order_id,
                shopify_order_id=shopify_order_id,
                tracking_id=tracking["number"],
                parcel_service=tracking["company"],
                tracking_url=tracking["url"],
                notify_via_email=notify,
            )
        except Exception as exc:
            logger.exception("Fulfillment tracking sync failed: %s", exc)
            WebhookService._save_fulfillment_event(
                event,
                topic=topic,
                shopify_order_id=shopify_order_id,
                digistore_order_id=order.digistore_order_id,
                tracking_number=tracking["number"],
                parcel_service=tracking["company"],
                tracking_url=tracking["url"],
                notify_via_email=notify,
                payload=payload,
                processed=False,
                error=f"digistore_exception: {exc}",
            )
            return {"ok": False, "error": "digistore_exception"}

        applied_carrier = str(
            (result.get("tracking") or {}).get("parcel_service")
            or tracking["company"]
        )

        if not result.get("ok"):
            WebhookService._save_fulfillment_event(
                event,
                topic=topic,
                shopify_order_id=shopify_order_id,
                digistore_order_id=order.digistore_order_id,
                tracking_number=tracking["number"],
                parcel_service=applied_carrier,
                tracking_url=tracking["url"],
                notify_via_email=notify,
                payload=payload,
                processed=False,
                error=str(result.get("error") or "updateDelivery_failed"),
            )
            return {
                "ok": False,
                "error": result.get("error") or "updateDelivery_failed",
            }

        WebhookService._save_fulfillment_event(
            event,
            topic=topic,
            shopify_order_id=shopify_order_id,
            digistore_order_id=order.digistore_order_id,
            tracking_number=tracking["number"],
            parcel_service=applied_carrier,
            tracking_url=tracking["url"],
            notify_via_email=notify,
            payload=payload,
            processed=True,
            error="",
        )
        return {"ok": True, "delivery_id": result.get("delivery_id")}
