package com.walkguide.service;

import com.walkguide.dto.request.LocationUpdateRequest;
import com.walkguide.dto.response.LocationResponse;
import com.walkguide.entity.LocationHistory;
import com.walkguide.entity.User;
import com.walkguide.enums.ActivityLogType;
import com.walkguide.enums.PairingStatus;
import com.walkguide.exception.ResourceNotFoundException;
import com.walkguide.repository.*;
import com.walkguide.websocket.LocationBroadcaster;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.Optional;

@Service
@RequiredArgsConstructor
@Slf4j
public class LocationService {

    private final LocationHistoryRepository locationHistoryRepository;
    private final GeofenceConfigRepository geofenceConfigRepository;
    private final PairingRelationRepository pairingRelationRepository;
    private final UserRepository userRepository;
    private final ActivityLogService activityLogService;
    private final FcmService fcmService;

    // ✅ BARU: WebSocket broadcaster untuk real-time location ke Guardian
    private final LocationBroadcaster locationBroadcaster;

    public LocationResponse updateLocation(Long userId, LocationUpdateRequest req) {
        LocationHistory loc = LocationHistory.builder()
                .userId(userId)
                .lat(req.getLat())
                .lng(req.getLng())
                .accuracy(req.getAccuracy())
                .speed(req.getSpeed())
                .heading(req.getHeading())
                .build();
        loc = locationHistoryRepository.save(loc);

        LocationResponse response = toResponse(loc);

        // ✅ BROADCAST real-time ke Guardian via WebSocket
        // Guardian subscribe ke /topic/location/{userId}
        locationBroadcaster.broadcastLocation(userId, response);
        log.debug("[LOCATION] Broadcast to Guardian | userId={} lat={} lng={}",
                userId, req.getLat(), req.getLng());

        // Cek geofence
        checkGeofence(userId, req.getLat(), req.getLng());

        return response;
    }

    public Optional<LocationResponse> getLastLocation(Long userId) {
        return locationHistoryRepository
                .findTopByUserIdOrderByCreatedAtDesc(userId)
                .map(this::toResponse);
    }

    // Untuk Guardian: ambil last location user yang dipair
    public Optional<LocationResponse> getLastLocationForGuardian(Long guardianId) {
        return pairingRelationRepository
                .findByGuardian_IdAndStatus(guardianId, PairingStatus.ACTIVE)
                .flatMap(p -> locationHistoryRepository
                        .findTopByUserIdOrderByCreatedAtDesc(p.getUser().getId()))
                .map(this::toResponse);
    }

    public Page<LocationResponse> getLocationHistory(Long userId, Pageable pageable) {
        return locationHistoryRepository
                .findByUserIdOrderByCreatedAtDesc(userId, pageable)
                .map(this::toResponse);
    }

    // ========== GEOFENCE ==========

    private void checkGeofence(Long userId, double lat, double lng) {
        geofenceConfigRepository.findByUserId(userId).ifPresent(cfg -> {
            if (!cfg.getEnabled() || cfg.getCenterLat() == null) return;

            double dist = haversineMeters(cfg.getCenterLat(), cfg.getCenterLng(), lat, lng);

            if (dist > cfg.getRadiusMeters()) {
                // User keluar geofence — beri tahu Guardian
                pairingRelationRepository.findByUser_IdAndStatus(userId, PairingStatus.ACTIVE)
                        .ifPresent(pairing -> {
                            User guardian = pairing.getGuardian();
                            User user = pairing.getUser();
                            fcmService.sendHighPriority(
                                    guardian.getFcmToken(),
                                    "⚠️ User Keluar Area Aman",
                                    user.getDisplayName() + " meninggalkan area geofence! " +
                                    String.format("%.0fm dari pusat area", dist),
                                    Map.of("type", "GEOFENCE_EXIT",
                                           "userId", String.valueOf(userId),
                                           "lat", String.valueOf(lat),
                                           "lng", String.valueOf(lng))
                            );
                            activityLogService.createLog(user, ActivityLogType.GEOFENCE_EXIT,
                                    String.format("User keluar area geofence (%.0fm dari pusat)", dist), null);
                            log.info("[GEOFENCE] User {} keluar area ({:.0f}m)", userId, dist);
                        });
            }
        });
    }

    /** Haversine formula — jarak antara 2 koordinat dalam meter */
    public static double haversineMeters(double lat1, double lng1, double lat2, double lng2) {
        final double R = 6371000; // radius bumi dalam meter
        double dLat = Math.toRadians(lat2 - lat1);
        double dLng = Math.toRadians(lng2 - lng1);
        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
                * Math.sin(dLng / 2) * Math.sin(dLng / 2);
        return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    }

    private LocationResponse toResponse(LocationHistory l) {
        return LocationResponse.builder()
                .id(l.getId()).lat(l.getLat()).lng(l.getLng())
                .accuracy(l.getAccuracy()).speed(l.getSpeed()).heading(l.getHeading())
                .createdAt(l.getCreatedAt()).build();
    }
}
