package com.zioinfo.mall.zone;

import com.zioinfo.mall.common.ApiResponse;
import com.zioinfo.mall.store.MallStore;
import com.zioinfo.mall.store.mapper.StoreMapper;
import com.zioinfo.mall.zone.mapper.ZoneMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 배송권역 API — /api/mall/zone.
 * ZIP/좌표 조회(고객 진입 필터, 공개)는 가용 매장·배송 가능 여부·최근접 매장을 판정한다(Floom 패턴).
 */
@RestController
@RequestMapping("/api/mall/zone")
@RequiredArgsConstructor
public class ZoneController {

    private final ZoneMapper mapper;
    private final StoreMapper storeMapper;

    /**
     * ZIP 진입 조회 — 공개. 해당 ZIP을 커버하는 매장 목록과 deliverable 판정.
     * 고객 좌표(ZIP 대표좌표)를 알면 최근접(haversine) 우선 정렬한다.
     * 응답: { zip, deliverable, stores:[{storeId,storeName,radiusMi,distanceMi}] }
     */
    @GetMapping("/lookup")
    public ApiResponse<Map<String, Object>> lookup(@RequestParam String zip) {
        double[] origin = resolveZipCoords(zip);   // null 가능
        List<MallZone> zones = mapper.findByZip(zip);
        List<Map<String, Object>> stores = new ArrayList<>();
        for (MallZone z : zones) {
            MallStore s = storeMapper.findById(z.getStoreId());
            if (s == null || Boolean.FALSE.equals(s.getActive())) continue;
            Map<String, Object> m = new LinkedHashMap<>();
            m.put("storeId", s.getId());
            m.put("storeCode", s.getCode());
            m.put("storeName", s.getName());
            m.put("radiusMi", z.getRadiusMi());
            m.put("sameDayCutoff", s.getSameDayCutoff());
            m.put("timezone", s.getTimezone());
            m.put("distanceMi", origin != null ? distanceMi(origin, s) : null);
            stores.add(m);
        }
        // 최근접 우선 정렬(거리 미상은 뒤로)
        stores.sort(Comparator.comparingDouble(m -> {
            Object d = m.get("distanceMi");
            return d == null ? Double.MAX_VALUE : ((Number) d).doubleValue();
        }));
        Map<String, Object> out = new LinkedHashMap<>();
        out.put("zip", zip);
        out.put("deliverable", !stores.isEmpty());
        out.put("stores", stores);
        return ApiResponse.ok(out);
    }

    /**
     * 최근접 지점 연결 — 공개. 고객 위치(zip 또는 lat/lng) 기준 haversine 최근접 매장 반환.
     * 응답: { origin:{lat,lng,source}, nearest:{storeId,storeName,distanceMi,deliverable}, stores:[...정렬...] }
     */
    @GetMapping("/nearest")
    public ApiResponse<Map<String, Object>> nearest(
            @RequestParam(required = false) String zip,
            @RequestParam(required = false) Double lat,
            @RequestParam(required = false) Double lng) {

        double[] origin = null;
        String source = null;
        if (lat != null && lng != null) {
            origin = new double[]{lat, lng};
            source = "coords";
        } else if (zip != null && !zip.isBlank()) {
            origin = resolveZipCoords(zip);
            source = origin != null ? "zip" : null;
        }
        if (origin == null) {
            return ApiResponse.fail("위치를 확인할 수 없습니다. zip 또는 lat/lng를 제공하세요.");
        }

        List<MallStore> all = storeMapper.findAll(true);
        List<Map<String, Object>> ranked = new ArrayList<>();
        for (MallStore s : all) {
            if (s.getLat() == null || s.getLng() == null) continue;
            double dist = distanceMi(origin, s);
            Map<String, Object> m = new LinkedHashMap<>();
            m.put("storeId", s.getId());
            m.put("storeCode", s.getCode());
            m.put("storeName", s.getName());
            m.put("city", s.getCity());
            m.put("state", s.getState());
            m.put("distanceMi", round1(dist));
            m.put("radiusMi", s.getDeliveryRadiusMi());
            boolean deliverable = s.getDeliveryRadiusMi() != null
                    && dist <= s.getDeliveryRadiusMi().doubleValue();
            m.put("deliverable", deliverable);
            m.put("sameDayCutoff", s.getSameDayCutoff());
            ranked.add(m);
        }
        ranked.sort(Comparator.comparingDouble(m -> ((Number) m.get("distanceMi")).doubleValue()));

        Map<String, Object> out = new LinkedHashMap<>();
        Map<String, Object> originOut = new LinkedHashMap<>();
        originOut.put("lat", origin[0]);
        originOut.put("lng", origin[1]);
        originOut.put("source", source);
        out.put("origin", originOut);
        out.put("nearest", ranked.isEmpty() ? null : ranked.get(0));
        out.put("stores", ranked);
        return ApiResponse.ok(out);
    }

    /** 배송지역(당일배송 시 목록) — 공개 스토어프론트 안내용. */
    @GetMapping("/areas")
    public ApiResponse<List<MallDeliveryArea>> areas() {
        return ApiResponse.ok(mapper.findAllAreas());
    }

    @GetMapping
    @PreAuthorize("hasAnyRole('ADMIN','MANAGER')")
    public ApiResponse<List<MallZone>> list(@RequestParam(required = false) Long storeId) {
        return ApiResponse.ok(storeId != null ? mapper.findByStore(storeId) : mapper.findAll());
    }

    @PostMapping
    @PreAuthorize("hasAnyRole('ADMIN','MANAGER')")
    public ApiResponse<MallZone> create(@RequestBody MallZone z) {
        mapper.insert(z);
        return ApiResponse.ok(z);
    }

    @DeleteMapping("/{id}")
    @PreAuthorize("hasAnyRole('ADMIN','MANAGER')")
    public ApiResponse<String> delete(@PathVariable Long id) {
        mapper.delete(id);
        return ApiResponse.ok("deleted");
    }

    // ---------------------------------------------------------------------
    // 내부 유틸
    // ---------------------------------------------------------------------

    /** ZIP → 대표좌표. 배송지역 → 권역(zone 매장 좌표) 순으로 근사. 미상 시 null. */
    private double[] resolveZipCoords(String zip) {
        MallDeliveryArea a = mapper.findAreaByZip(zip);
        if (a != null && a.getLat() != null && a.getLng() != null) {
            return new double[]{a.getLat().doubleValue(), a.getLng().doubleValue()};
        }
        // zone 매핑된 매장의 좌표로 근사(같은 권역 대표점)
        List<MallZone> zones = mapper.findByZip(zip);
        for (MallZone z : zones) {
            MallStore s = storeMapper.findById(z.getStoreId());
            if (s != null && s.getLat() != null && s.getLng() != null) {
                return new double[]{s.getLat().doubleValue(), s.getLng().doubleValue()};
            }
        }
        return null;
    }

    private double distanceMi(double[] origin, MallStore s) {
        return round1(haversineMi(origin[0], origin[1],
                s.getLat().doubleValue(), s.getLng().doubleValue()));
    }

    /** Haversine 거리(마일). */
    private static double haversineMi(double lat1, double lng1, double lat2, double lng2) {
        double R = 3958.7613; // 지구 반경(마일)
        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 static double round1(double v) {
        return Math.round(v * 10.0) / 10.0;
    }
}
