import logging
import math

from django.conf import settings

logger = logging.getLogger('api')


def _haversine_km(lat1, lon1, lat2, lon2):
    r_km = 6371.0
    dlat = math.radians(lat2 - lat1)
    dlon = math.radians(lon2 - lon1)
    a = (
        math.sin(dlat / 2) ** 2
        + math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.sin(dlon / 2) ** 2
    )
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
    return r_km * c

_firebase_app = None


def _ensure_firebase():
    global _firebase_app
    if _firebase_app is not None:
        logger.info('FCM firebase app ready source=cache')
        return _firebase_app
    path = getattr(settings, 'FIREBASE_CREDENTIALS_PATH', '') or ''
    if not path:
        logger.warning('FCM firebase disabled reason=missing_credentials_path')
        return None
    try:
        import firebase_admin
        from firebase_admin import credentials
    except ImportError:
        logger.warning('firebase_admin not installed; push disabled.')
        return None
    try:
        _firebase_app = firebase_admin.get_app()
        logger.info('FCM firebase app ready source=get_app')
        return _firebase_app
    except ValueError:
        pass
    try:
        cred = credentials.Certificate(path)
        _firebase_app = firebase_admin.initialize_app(cred)
        logger.info('FCM firebase app initialized credentials=%s', path)
    except Exception as exc:
        logger.exception('Firebase init failed: %s', exc)
        return None
    return _firebase_app


def send_fcm_data_to_user(user, data: dict, title: str | None = None, body: str | None = None):
    """
    Send FCM using the user's token from UserPushDevice (1:1).
    `data` values must be strings for FCM data payload.
    """
    logger.info(
        'FCM flow start user=%s event=%s payload_keys=%s has_title=%s has_body=%s',
        user.id,
        str(data.get('event_type', '')),
        sorted([str(k) for k in data.keys()]),
        bool(title),
        bool(body),
    )
    app = _ensure_firebase()
    if not app:
        logger.warning('FCM skip user=%s reason=firebase_unavailable', user.id)
        return False
    try:
        from accounts.models import UserPushDevice
        from firebase_admin import messaging
    except ImportError:
        logger.warning('FCM skip user=%s reason=import_error', user.id)
        return False
    token = ''
    try:
        dev = UserPushDevice.objects.filter(user=user).first()
        if dev and dev.fcm_token:
            token = dev.fcm_token
    except Exception:
        pass
    if not token:
        logger.info('FCM skip user=%s reason=no_token', user.id)
        return False
    logger.info('FCM token loaded user=%s token_prefix=%s', user.id, token[:16])
    str_data = {str(k): '' if v is None else str(v) for k, v in data.items()}
    # Ensure display + deep-link fields are always present for clients that
    # expect notification{title,body} + data{type,tag} (e.g. new_message).
    if title and 'title' not in str_data:
        str_data['title'] = str(title)
    if body and 'body' not in str_data:
        str_data['body'] = str(body)
    message = messaging.Message(
        token=token,
        data=str_data,
        notification=messaging.Notification(title=title, body=body) if title or body else None,
        android=messaging.AndroidConfig(priority='high'),
        apns=messaging.APNSConfig(headers={'apns-priority': '10'}),
    )
    try:
        msg_id = messaging.send(message, app=app)
        logger.info(
            'FCM sent user=%s token_prefix=%s event=%s msg_id=%s',
            user.id,
            token[:16],
            str_data.get('event_type', ''),
            msg_id,
        )
        return True
    except Exception as exc:
        logger.warning(
            'FCM send failed user=%s token_prefix=%s event=%s error=%s',
            user.id,
            token[:16],
            str_data.get('event_type', ''),
            exc,
        )
        return False


def notify_nearby_transporters_new_shipment(shipment) -> int:
    """
    FCM NEARBY_LOAD to transporters with Traccar position within radius of pickup (capped).
    Returns number of notifications attempted.
    """
    from accounts.models import TransporterProfile, UserRole
    from api.traccar import get_latest_device_position, get_latest_positions_map

    if shipment.pickup_lat is None or shipment.pickup_lon is None:
        return 0
    try:
        plat = float(shipment.pickup_lat)
        plon = float(shipment.pickup_lon)
    except (TypeError, ValueError):
        return 0

    radius = float(getattr(settings, 'NEARBY_LOAD_NOTIFY_RADIUS_KM', 50))
    cap = int(getattr(settings, 'NEARBY_PUBLISH_NOTIFY_MAX', 50))

    try:
        positions_map = get_latest_positions_map()
    except Exception as exc:
        logger.debug('Traccar bulk positions unavailable: %s', exc)
        positions_map = {}

    candidates = []
    qs = (
        TransporterProfile.objects.select_related('user')
        .filter(user__role__role=UserRole.Role.TRANSPORTER, documents_verified=True)
        .exclude(tc_id__isnull=True)
        .exclude(tc_id='')
    )[:500]
    for profile in qs.iterator():
        tc_id = str(profile.tc_id)
        pos = positions_map.get(tc_id)
        if not pos:
            try:
                pos = get_latest_device_position(profile.tc_id)
            except Exception as exc:
                logger.debug('Traccar position skip user=%s: %s', profile.user_id, exc)
                continue
        lat, lon = pos.get('latitude'), pos.get('longitude')
        if lat is None or lon is None:
            continue
        try:
            d = _haversine_km(float(lat), float(lon), plat, plon)
        except (TypeError, ValueError):
            continue
        if d <= radius:
            candidates.append((d, profile.user_id))

    candidates.sort(key=lambda x: x[0])
    n = 0
    for _, uid in candidates[:cap]:
        notify_users(
            [uid],
            'NEARBY_LOAD',
            {
                'shipment_id': str(shipment.id),
                'pickup_lat': str(plat),
                'pickup_lon': str(plon),
            },
            title='New load nearby',
            body='A published load is available near your last reported position.',
        )
        n += 1
    return n


def notify_users(user_ids, event_type: str, payload: dict, title: str | None = None, body: str | None = None):
    from django.contrib.auth import get_user_model
    from core.models import Notification
    User = get_user_model()
    base = {'event_type': event_type, **payload}
    notif_type = (event_type or '').strip().lower()
    notif_title = (title or '').strip() or 'Notification'
    notif_message = (body or '').strip() or notif_title
    safe_user_ids = [uid for uid in user_ids if uid]
    logger.info(
        'Notify pipeline start event=%s users=%s payload_keys=%s title=%s',
        event_type,
        safe_user_ids,
        sorted([str(k) for k in (payload or {}).keys()]),
        notif_title,
    )
    for uid in user_ids:
        if not uid:
            logger.warning('Notify pipeline skip event=%s reason=empty_user_id', event_type)
            continue
        try:
            u = User.objects.get(pk=uid)
        except User.DoesNotExist:
            logger.warning('Notify pipeline skip event=%s user=%s reason=user_not_found', event_type, uid)
            continue
        notif = Notification.objects.create(
            title=notif_title,
            message=notif_message,
            type=notif_type,
            user=u,
            data=payload or None,
            seen=False,
        )
        logger.info('Notify DB created event=%s user=%s notification_id=%s', event_type, u.id, notif.id)
        sent = send_fcm_data_to_user(u, base, title=title, body=body)
        logger.info('Notify push result event=%s user=%s sent=%s', event_type, u.id, sent)
