package com.erp.service;

import com.erp.common.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.*;

/**
 * 차량 운행 이력 + 리포트 서비스
 * Node.js: vehicleTripService.ts + vehicleReportService.ts
 * Tables: vehicle_trip_summary (PK: trip_id), vehicle_location_history
 */
@Service
@Slf4j
public class VehicleTripService extends BaseService {

    @Autowired private CommonService commonService;

    // ═══════════════════════════════════════════════════════════
    // Trip operations
    // ═══════════════════════════════════════════════════════════

    /**
     * POST /trip/start — 운행 시작
     * tripId = "TRIP-{timestamp}-{uuid8}" 자동 생성
     */
    @Transactional
    public Map<String, Object> startTrip(Map<String, Object> params) {
        String tripId = "TRIP-" + System.currentTimeMillis() + "-"
                + UUID.randomUUID().toString().substring(0, 8);
        params.put("trip_id", tripId);
        params.put("trip_status", "start");

        // 1. vehicle_trip_summary INSERT
        sqlSession.insert("vehicleTrip.insertVehicleTrip", params);

        // 2. 시작 위치 INSERT
        sqlSession.insert("vehicleTrip.insertVehicleTripLocation", params);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("trip_id", tripId);
        result.put("start_location", Map.of(
                "latitude", params.get("latitude"),
                "longitude", params.get("longitude")
        ));
        return result;
    }

    /**
     * POST /trip/end — 운행 종료
     */
    @Transactional
    public Map<String, Object> endTrip(Map<String, Object> params) {
        // 1. 활성 운행 확인
        Map<String, Object> activeTrip = sqlSession.selectOne("vehicleTrip.getVehicleTripActiveInfo", params);
        if (activeTrip == null) {
            throw new RuntimeException("활성 운행을 찾을 수 없습니다.");
        }

        // 2. 종료 위치 INSERT
        Map<String, Object> locParams = new LinkedHashMap<>(params);
        locParams.put("vehicle_id", activeTrip.get("vehicle_id"));
        locParams.put("trip_status", "end");
        locParams.put("departure", activeTrip.get("departure"));
        locParams.put("arrival", activeTrip.get("arrival"));
        locParams.put("departure_name", activeTrip.get("departure_name"));
        locParams.put("destination_name", activeTrip.get("destination_name"));
        sqlSession.insert("vehicleTrip.insertVehicleTripLocation", locParams);

        // 3. 통계 조회 (COUNT, duration)
        Map<String, Object> stats = sqlSession.selectOne("vehicleTrip.getVehicleTripLocationStats", params);
        int locationCount = ((Number) stats.get("location_count")).intValue();
        int durationMinutes = ((Number) stats.get("duration_minutes")).intValue();

        // 4. 모든 위치 좌표 → Haversine 총 거리 계산
        List<Map<String, Object>> locations = sqlSession.selectList("vehicleTrip.getVehicleTripLocationListAll", params);
        double totalDistance = 0;
        for (int i = 1; i < locations.size(); i++) {
            totalDistance += haversine(
                    toDouble(locations.get(i - 1).get("latitude")),
                    toDouble(locations.get(i - 1).get("longitude")),
                    toDouble(locations.get(i).get("latitude")),
                    toDouble(locations.get(i).get("longitude"))
            );
        }
        double roundedDistance = Math.round(totalDistance * 1000.0) / 1000.0;

        // 5. vehicle_trip_summary 업데이트
        Map<String, Object> updateParams = new LinkedHashMap<>();
        updateParams.put("trip_id", params.get("trip_id"));
        updateParams.put("company_code", params.get("company_code"));
        updateParams.put("total_distance", roundedDistance);
        updateParams.put("duration_minutes", durationMinutes);
        updateParams.put("location_count", locationCount);
        sqlSession.update("vehicleTrip.updateVehicleTripEnd", updateParams);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("trip_id", params.get("trip_id"));
        result.put("total_distance", roundedDistance);
        result.put("duration_minutes", durationMinutes);
        result.put("location_count", locationCount);
        return result;
    }

    /**
     * POST /trip/location — 연속 위치 추적
     */
    @Transactional
    public Map<String, Object> addLocation(Map<String, Object> params) {
        // 1. 활성 운행 확인
        Map<String, Object> activeTrip = sqlSession.selectOne("vehicleTrip.getVehicleTripActiveInfo", params);
        if (activeTrip == null) {
            throw new RuntimeException("활성 운행을 찾을 수 없습니다.");
        }

        // 2. 이전 위치 조회 → 거리 계산
        Map<String, Object> prevLoc = sqlSession.selectOne("vehicleTrip.getVehicleTripLastLocation", params);
        double distanceFromPrev = 0;
        if (prevLoc != null) {
            distanceFromPrev = haversine(
                    toDouble(prevLoc.get("latitude")), toDouble(prevLoc.get("longitude")),
                    toDouble(params.get("latitude")), toDouble(params.get("longitude"))
            );
            distanceFromPrev = Math.round(distanceFromPrev * 1000.0) / 1000.0;
        }

        // 3. 위치 INSERT
        Map<String, Object> locParams = new LinkedHashMap<>(params);
        locParams.put("vehicle_id", activeTrip.get("vehicle_id"));
        locParams.put("trip_status", "tracking");
        locParams.put("departure", activeTrip.get("departure"));
        locParams.put("arrival", activeTrip.get("arrival"));
        locParams.put("departure_name", activeTrip.get("departure_name"));
        locParams.put("destination_name", activeTrip.get("destination_name"));
        locParams.put("distance_from_prev", distanceFromPrev > 0 ? distanceFromPrev : null);
        sqlSession.insert("vehicleTrip.insertVehicleTripLocation", locParams);

        // 4. location_count + 1
        sqlSession.update("vehicleTrip.updateVehicleTripLocationCount", params);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("location_id", locParams.get("id"));
        result.put("distance_from_prev", distanceFromPrev);
        return result;
    }

    /**
     * GET /trips — 운행 이력 목록
     * 응답: { data: [...], total: N }
     */
    public Map<String, Object> getTripList(Map<String, Object> params) {
        convertToInt(params, "vehicle_id");
        commonService.applyPagination(params);
        Number cntNum = sqlSession.selectOne("vehicleTrip.getVehicleTripListCnt", params);
        int total = cntNum != null ? cntNum.intValue() : 0;
        List<Map<String, Object>> data = sqlSession.selectList("vehicleTrip.getVehicleTripList", params);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("data", data);
        result.put("total", total);
        return result;
    }

    /**
     * GET /trips/:tripId — 운행 상세 (경로 포함)
     * 응답: { summary, route }
     */
    public Map<String, Object> getTripDetail(Map<String, Object> params) {
        Map<String, Object> summary = sqlSession.selectOne("vehicleTrip.getVehicleTripInfo", params);
        if (summary == null) return null;

        List<Map<String, Object>> route = sqlSession.selectList("vehicleTrip.getVehicleTripRouteList", params);
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("summary", summary);
        result.put("route", route);
        return result;
    }

    /**
     * GET /trip/active — 현재 진행 중 운행
     * 응답: { data, hasActiveTrip }
     */
    public Map<String, Object> getActiveTrip(Map<String, Object> params) {
        Map<String, Object> trip = sqlSession.selectOne("vehicleTrip.getVehicleTripActiveByUser", params);
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("data", trip);
        result.put("has_active_trip", trip != null);
        return result;
    }

    /**
     * POST /trip/cancel — 운행 취소
     * null 반환 시 404
     */
    @Transactional
    public Map<String, Object> cancelTrip(Map<String, Object> params) {
        Map<String, Object> activeTrip = sqlSession.selectOne("vehicleTrip.getVehicleTripActiveInfo", params);
        if (activeTrip == null) return null;
        sqlSession.update("vehicleTrip.updateVehicleTripCancel", params);
        return activeTrip;
    }

    // ═══════════════════════════════════════════════════════════
    // Reports
    // ═══════════════════════════════════════════════════════════

    /** GET /reports/daily — 일별 통계 (기본 최근 30일) */
    public Map<String, Object> getDailyReport(Map<String, Object> params) {
        convertToInt(params, "vehicle_id");
        LocalDate today = LocalDate.now();
        String startDate = params.get("start_date") != null && !params.get("start_date").toString().isBlank()
                ? params.get("start_date").toString() : today.minusDays(30).toString();
        String endDate = params.get("end_date") != null && !params.get("end_date").toString().isBlank()
                ? params.get("end_date").toString() : today.toString();
        params.put("start_date", startDate);
        params.put("end_date", endDate);

        List<Map<String, Object>> data = sqlSession.selectList("vehicleTrip.getVehicleTripDailyReport", params);
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("start_date", startDate);
        result.put("end_date", endDate);
        result.put("data", data);
        return result;
    }

    /** GET /reports/weekly — 주별 통계 */
    public Map<String, Object> getWeeklyReport(Map<String, Object> params) {
        convertToInt(params, "vehicle_id");
        int year = params.get("year") != null
                ? toInt(params.get("year")) : LocalDate.now().getYear();
        int month = params.get("month") != null
                ? toInt(params.get("month")) : LocalDate.now().getMonthValue();
        params.put("year", year);
        params.put("month", month);

        List<Map<String, Object>> data = sqlSession.selectList("vehicleTrip.getVehicleTripWeeklyReport", params);
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("year", year);
        result.put("month", month);
        result.put("data", data);
        return result;
    }

    /** GET /reports/monthly — 월별 통계 */
    public Map<String, Object> getMonthlyReport(Map<String, Object> params) {
        convertToInt(params, "vehicle_id");
        int year = params.get("year") != null
                ? toInt(params.get("year")) : LocalDate.now().getYear();
        params.put("year", year);

        List<Map<String, Object>> data = sqlSession.selectList("vehicleTrip.getVehicleTripMonthlyReport", params);
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("year", year);
        result.put("data", data);
        return result;
    }

    /** GET /reports/summary — 요약 통계 (period: today/week/month/year) */
    public Map<String, Object> getSummaryReport(Map<String, Object> params) {
        if (params.get("period") == null || params.get("period").toString().isBlank()) {
            params.put("period", "today");
        }
        Map<String, Object> row = sqlSession.selectOne("vehicleTrip.getVehicleTripSummaryReport", params);
        if (row == null) row = new LinkedHashMap<>();

        long totalTrips = toLong(row.get("total_trips"));
        long completedTrips = toLong(row.get("completed_trips"));
        double completionRate = totalTrips > 0
                ? Math.round((double) completedTrips / totalTrips * 1000.0) / 10.0 : 0.0;

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("period", params.get("period"));
        result.put("total_trips", totalTrips);
        result.put("completed_trips", completedTrips);
        result.put("active_trips", toLong(row.get("active_trips")));
        result.put("cancelled_trips", toLong(row.get("cancelled_trips")));
        result.put("completion_rate", completionRate);
        result.put("total_distance", toDouble(row.get("total_distance")));
        result.put("total_duration", toLong(row.get("total_duration")));
        result.put("avg_distance", toDouble(row.get("avg_distance")));
        result.put("avg_duration", toDouble(row.get("avg_duration")));
        result.put("active_drivers", toLong(row.get("active_drivers")));
        return result;
    }

    /** GET /reports/by-driver — 운전자별 통계 */
    public List<Map<String, Object>> getDriverReport(Map<String, Object> params) {
        if (params.get("limit") == null) params.put("limit", 10);
        else convertToInt(params, "limit");
        return sqlSession.selectList("vehicleTrip.getVehicleTripDriverReport", params);
    }

    /** GET /reports/by-route — 구간별 통계 */
    public List<Map<String, Object>> getRouteReport(Map<String, Object> params) {
        if (params.get("limit") == null) params.put("limit", 10);
        else convertToInt(params, "limit");
        return sqlSession.selectList("vehicleTrip.getVehicleTripRouteReport", params);
    }

    // ═══════════════════════════════════════════════════════════
    // Utilities
    // ═══════════════════════════════════════════════════════════

    /** Haversine 거리 계산 (km) */
    private double haversine(double lat1, double lon1, double lat2, double lon2) {
        final int R = 6371;
        double dLat = Math.toRadians(lat2 - lat1);
        double dLon = Math.toRadians(lon2 - lon1);
        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
                * Math.sin(dLon / 2) * Math.sin(dLon / 2);
        return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    }

    private double toDouble(Object val) {
        if (val == null) return 0.0;
        if (val instanceof Number n) return n.doubleValue();
        try { return Double.parseDouble(val.toString()); } catch (Exception e) { return 0.0; }
    }

    private long toLong(Object val) {
        if (val == null) return 0L;
        if (val instanceof Number n) return n.longValue();
        try { return Long.parseLong(val.toString()); } catch (Exception e) { return 0L; }
    }

    private int toInt(Object val) {
        if (val == null) return 0;
        if (val instanceof Number n) return n.intValue();
        try { return Integer.parseInt(val.toString()); } catch (Exception e) { return 0; }
    }

    private void convertToInt(Map<String, Object> params, String key) {
        Object val = params.get(key);
        if (val instanceof String s && !s.isBlank()) {
            try { params.put(key, Integer.parseInt(s)); }
            catch (NumberFormatException e) { params.remove(key); }
        }
    }
}
