"""Admin-facing DHL shipping endpoints.

All routes require an admin JWT (via ``get_admin_user``). The router exposes:

- ``GET  /settings``                 — current (masked) credentials.
- ``PUT  /settings``                 — upsert credentials.
- ``POST /settings/test``            — test connection.
- ``POST /orders/{order_id}/ship``   — create a DHL shipment for an order and
                                       persist the tracking number on the
                                       ``orders`` row.
"""

from __future__ import annotations

import json
import logging
from typing import Any, Dict, Optional

from core.database import get_db
from dependencies.auth import get_admin_user
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from models.orders import Orders
from schemas.auth import UserResponse
from schemas.dhl import (
    DhlNotificationCheckResult,
    DhlNotificationList,
    DhlNotificationOut,
    DhlSettingsOut,
    DhlSettingsUpdate,
    DhlShipmentResult,
    DhlTestResult,
)
from services.audit_log import record_admin_event
from services.dhl import (
    create_shipment_for_order,
    get_settings,
    list_notifications,
    notify_if_status_changed,
    save_settings,
    test_connection,
)
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v1/admin/dhl", tags=["dhl-admin"])


@router.get("/settings", response_model=DhlSettingsOut)
async def read_settings(
    _admin: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
) -> DhlSettingsOut:
    settings = await get_settings(db)
    return DhlSettingsOut(**settings.masked())


@router.put("/settings", response_model=DhlSettingsOut)
async def update_settings(
    payload: DhlSettingsUpdate,
    request: Request,
    admin: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
) -> DhlSettingsOut:
    settings = await save_settings(
        db,
        api_key=payload.api_key,
        api_secret=payload.api_secret,
        account_number=payload.account_number,
        environment=payload.environment or "sandbox",
    )
    await record_admin_event(
        db,
        action="admin.dhl.settings_updated",
        admin_user_id=admin.id,
        admin_email=admin.email,
        details={"environment": settings.environment, "configured": settings.configured},
        request=request,
    )
    return DhlSettingsOut(**settings.masked())


@router.post("/settings/test", response_model=DhlTestResult)
async def test_dhl(
    _admin: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
) -> DhlTestResult:
    settings = await get_settings(db)
    result = await test_connection(settings)
    return DhlTestResult(**result)


def _parse_address(order: Orders) -> Dict[str, Any]:
    """Extract a best-effort shipping address dict from the stored JSON."""
    if not order.shipping_address_json:
        return {}
    try:
        data = json.loads(order.shipping_address_json)
    except (TypeError, ValueError):
        return {}
    if not isinstance(data, dict):
        return {}
    # Normalize frequently-used keys.
    return {
        "city": data.get("city") or data.get("City") or "",
        "country": data.get("country") or data.get("Country") or "SA",
        "postal_code": data.get("postal_code") or data.get("postalCode") or data.get("zip") or "",
        "line1": data.get("line1") or data.get("address_line1") or data.get("address") or "",
        "phone": data.get("phone") or "",
        "email": data.get("email") or "",
        "name": data.get("name") or data.get("full_name") or "",
    }


@router.post("/orders/{order_id}/ship", response_model=DhlShipmentResult)
async def create_order_shipment(
    order_id: int,
    request: Request,
    admin: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
) -> DhlShipmentResult:
    result = await db.execute(select(Orders).where(Orders.id == order_id))
    order = result.scalar_one_or_none()
    if order is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Order not found")

    settings = await get_settings(db)
    address = _parse_address(order)
    recipient_name = address.get("name") or order.billing_email or "Customer"

    shipment = await create_shipment_for_order(
        settings,
        order_number=order.order_number or f"order-{order.id}",
        recipient_name=recipient_name,
        recipient_address=address,
        weight_kg=1.0,
    )

    # Persist on the order.
    order.tracking_number = shipment["tracking_number"]
    # Reset the notification state whenever a new shipment is created for
    # the same order so the customer gets a fresh "created / picked up"
    # email even if a previous attempt was cancelled.
    order.last_notified_status = None
    # Track when we used DHL so the customer tracking page can estimate event
    # progression in stub mode. We append to notes rather than introducing a
    # new DB column for the MVP.
    note_line = (
        f"[dhl] carrier=DHL tracking={shipment['tracking_number']} "
        f"stub={shipment['stub']} env={settings.environment}"
    )
    order.notes = (order.notes + "\n" if order.notes else "") + note_line
    if order.status in (None, "pending", "processing", "paid"):
        order.status = "shipped"
    await db.commit()

    await record_admin_event(
        db,
        action="admin.dhl.shipment_created",
        admin_user_id=admin.id,
        admin_email=admin.email,
        details={
            "order_id": order.id,
            "order_number": order.order_number,
            "tracking_number": shipment["tracking_number"],
            "stub": shipment["stub"],
        },
        request=request,
    )

    # Fire-and-forget-ish: try to send the initial "shipment created" email
    # right away. Failures are logged to the delivery log but must not break
    # shipment creation for the admin.
    try:
        await notify_if_status_changed(db, order=order, settings=settings)
    except Exception:  # noqa: BLE001
        logger.exception(
            "[dhl] Initial notification after shipment create failed for order %s", order.id,
        )

    return DhlShipmentResult(
        order_id=order.id,
        tracking_number=shipment["tracking_number"],
        label_url=shipment.get("label_url"),
        stub=shipment["stub"],
        message=shipment["message"],
    )


def _serialize_notification(row) -> DhlNotificationOut:
    return DhlNotificationOut(
        id=row.id,
        order_id=row.order_id,
        order_number=row.order_number,
        tracking_number=row.tracking_number,
        recipient_email=row.recipient_email,
        previous_status=row.previous_status,
        new_status=row.new_status,
        event_description=row.event_description,
        event_location=row.event_location,
        event_timestamp=row.event_timestamp,
        success=row.success,
        error_message=row.error_message,
        stub=row.stub,
        created_at=row.created_at.isoformat() if row.created_at else None,
    )


@router.post(
    "/orders/{order_id}/check-tracking",
    response_model=DhlNotificationCheckResult,
)
async def check_tracking_and_notify(
    order_id: int,
    request: Request,
    force: bool = Query(False, description="Force-send even if status is unchanged"),
    admin: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
) -> DhlNotificationCheckResult:
    """Re-poll DHL for the given order and send an email if the status changed.

    This is idempotent — calling it repeatedly while the shipment is in the
    same status is a no-op (``reason="unchanged"``). Use ``force=true`` to
    resend regardless (useful for re-sending a failed email).
    """
    result = await db.execute(select(Orders).where(Orders.id == order_id))
    order = result.scalar_one_or_none()
    if order is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Order not found")

    settings = await get_settings(db)
    outcome = await notify_if_status_changed(db, order=order, settings=settings, force=force)

    await record_admin_event(
        db,
        action="admin.dhl.notification_checked",
        admin_user_id=admin.id,
        admin_email=admin.email,
        details={
            "order_id": order.id,
            "order_number": order.order_number,
            "tracking_number": outcome.get("tracking_number"),
            "sent": outcome.get("sent"),
            "reason": outcome.get("reason"),
            "status": outcome.get("status"),
            "force": force,
        },
        request=request,
    )
    return DhlNotificationCheckResult(**outcome)


@router.get("/notifications", response_model=DhlNotificationList)
async def list_dhl_notifications(
    order_id: Optional[int] = Query(None, description="Filter by order id"),
    tracking_number: Optional[str] = Query(None, description="Filter by tracking number"),
    success: Optional[bool] = Query(None, description="Filter by delivery success"),
    limit: int = Query(100, ge=1, le=500),
    _admin: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
) -> DhlNotificationList:
    rows = await list_notifications(
        db,
        order_id=order_id,
        tracking_number=tracking_number,
        success=success,
        limit=limit,
    )
    return DhlNotificationList(items=[_serialize_notification(r) for r in rows])