package com.rj.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rj.config.AmapProperties;
import com.rj.dto.NearbyMerchantItem;
import com.rj.dto.NearbyMerchantSearchResult;
import com.rj.entity.CustomerManagement;
import com.rj.service.ICustomerManagementService;
import com.rj.service.INearbyMerchantSearchService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * 逻辑参考 {@link com.rj.gaode  AllShopCollectorV5}：地理编码 + place/around 全行业周边搜索。
 */
@Slf4j
@Service
public class NearbyMerchantSearchServiceImpl implements INearbyMerchantSearchService {

    private static final String GEOCODE_URL = "https://restapi.amap.com/v3/geocode/geo";
    private static final String AROUND_URL = "https://restapi.amap.com/v3/place/around";
    private static final int AMAP_OFFSET_MAX = 50;
    private static final int AMAP_PAGE_DELAY_MS = 800;
    /** 与 customer_management 正面/侧面/生活照三字段一致 */
    private static final int MAX_PHOTO_URLS = 3;

    /** 高德采集写入客户时的默认门店/销售/阶段（可按业务调整） */
    private static final String DEFAULT_DEALERSHIP_ID = "1957424710587330562";
    private static final String DEFAULT_DEALERSHIP_NAME = "北京移多旺科技有限公司";
    private static final String DEFAULT_SALES_ID = "2";
    private static final String DEFAULT_SALES_NAME = "15810181776";
    private static final String DEFAULT_SALES_PHONE = "15810181776";
    private static final String DEFAULT_STAGE = "线索";

    private final RestTemplate restTemplate;
    private final ObjectMapper objectMapper;
    private final AmapProperties amapProperties;
    private final ICustomerManagementService customerManagementService;

    public NearbyMerchantSearchServiceImpl(RestTemplate restTemplate,
                                           ObjectMapper objectMapper,
                                           AmapProperties amapProperties,
                                           ICustomerManagementService customerManagementService) {
        this.restTemplate = restTemplate;
        this.objectMapper = objectMapper;
        this.amapProperties = amapProperties;
        this.customerManagementService = customerManagementService;
    }

    @Override
    public NearbyMerchantSearchResult searchNearbyMerchants(String city, String address,
                                                            int radiusMeters, int maxPage, int maxRecordsPerPage) {
        if (!StringUtils.hasText(amapProperties.getWebServiceKey())) {
            return NearbyMerchantSearchResult.badRequest("未配置高德 Web 服务 Key，请在配置中设置 amap.web-service-key");
        }
        if (!StringUtils.hasText(city) || !StringUtils.hasText(address)) {
            return NearbyMerchantSearchResult.badRequest("CITY 与 ADDRESS 不能为空");
        }
        if (radiusMeters <= 0) {
            return NearbyMerchantSearchResult.badRequest("RADIUS 须为正整数（米）");
        }
        if (maxPage < 1) {
            return NearbyMerchantSearchResult.badRequest("MAX_PAGE 须大于等于 1");
        }
        int offset = Math.min(AMAP_OFFSET_MAX, Math.max(1, maxRecordsPerPage));
        int pages = Math.min(maxPage, 100);

        try {
            String location = geocodeToLocation(city.trim(), address.trim());
            List<NearbyMerchantItem> all = new ArrayList<>();
            String totalCount = null;
            for (int page = 1; page <= pages; page++) {
                String json = fetchAroundPage(location, radiusMeters, offset, page);
                JsonNode root = objectMapper.readTree(json);
                if (!"1".equals(root.path("status").asText())) {
                    String info = root.path("info").asText("高德接口返回失败");
                    return NearbyMerchantSearchResult.badRequest("周边搜索失败：" + info);
                }
                if (totalCount == null) {
                    totalCount = root.path("count").asText(null);
                }
                JsonNode pois = root.path("pois");
                if (pois.isArray()) {
                    for (JsonNode p : pois) {
                        String tel = text(p, "tel").trim();
                        if (!StringUtils.hasText(tel)) {
                            continue;
                        }
                        all.add(new NearbyMerchantItem(
                                text(p, "name"),
                                text(p, "address"),
                                tel,
                                text(p, "type"),
                                text(p, "location"),
                                text(p, "id"),
                                text(p, "distance"),
                                text(p, "typecode"),
                                text(p, "business_area"),
                                extractPhotoUrls(p)
                        ));
                    }
                }
                if (page < pages) {
                    try {
                        Thread.sleep(AMAP_PAGE_DELAY_MS);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        return NearbyMerchantSearchResult.serverError("查询被中断");
                    }
                }
            }
            String cTrim = city.trim();
            String aTrim = address.trim();
            String report = buildDetailedReport(cTrim, aTrim, radiusMeters, location, totalCount, all);
            log.info("附近商家检索明细（与 AllShopCollectorV5 格式对齐）\n{}", report);
            int customerWriteCount = persistMerchantsAsCustomers(all, cTrim, aTrim);
            if (customerWriteCount > 0) {
                log.info("高德采集结果已同步 customer_management，成功 {} 条（按 contact 新增或更新）", customerWriteCount);
            }
            return NearbyMerchantSearchResult.ok(location, all, totalCount, report, customerWriteCount);
        } catch (IllegalArgumentException e) {
            return NearbyMerchantSearchResult.badRequest(e.getMessage());
        } catch (Exception e) {
            log.warn("附近商家检索异常: {}", e.getMessage());
            return NearbyMerchantSearchResult.serverError("附近商家检索异常：" + e.getMessage());
        }
    }

    /**
     * 地理编码。URL 拼法与 {@link com.rj.gaode  AllShopCollectorV5} 一致：
     * 使用 {@link URLEncoder#encode(String, java.nio.charset.Charset)} 处理中文查询参数。
     * Spring {@code UriComponentsBuilder} 的编码规则与 {@code URLEncoder} 不一致，易导致高德返回 ENGINE_RESPONSE_DATA_ERROR（如 infocode=30001）。
     * 首次失败后不传 {@code city}，仅用合并后的完整地址再请求一次。
     */
    private String geocodeToLocation(String city, String address) throws Exception {
        String json = fetchGeocode(city, address);
        String loc = parseGeocodeLocation(json);
        if (loc != null) {
            return loc;
        }

        String merged = mergeCityAddress(city, address);
        log.debug("地理编码首次未返回坐标，不传 city、使用合并地址重试");
        json = fetchGeocode(null, merged);
        loc = parseGeocodeLocation(json);
        if (loc != null) {
            return loc;
        }

        throw geocodeFailureException(json);
    }

    /** 与 AllShopCollectorV5 相同：key、c（可选）、address、output；city 与 address 经 UTF-8 URLEncoder。无 city 时不带 city 参数。 */
    private String fetchGeocode(String city, String address) {
        String key = amapProperties.getWebServiceKey();
        StringBuilder sb = new StringBuilder(GEOCODE_URL);
        sb.append("?key=").append(key);
        if (StringUtils.hasText(city)) {
            sb.append("&city=").append(URLEncoder.encode(city, StandardCharsets.UTF_8));
        }
        sb.append("&address=").append(URLEncoder.encode(address, StandardCharsets.UTF_8));
        sb.append("&output=json");
        String url = sb.toString();
        String json = restTemplate.getForObject(URI.create(url), String.class);
        if (json == null) {
            throw new IllegalStateException("高德地理编码返回空响应");
        }
        return json;
    }

    /** status=1 且存在 location 时返回坐标，否则返回 null。 */
    private String parseGeocodeLocation(String json) throws Exception {
        JsonNode root = objectMapper.readTree(json);
        if (!"1".equals(root.path("status").asText())) {
            return null;
        }
        JsonNode geocodes = root.path("geocodes");
        if (!geocodes.isArray() || geocodes.isEmpty()) {
            return null;
        }
        String loc = geocodes.get(0).path("location").asText("");
        return StringUtils.hasText(loc) ? loc : null;
    }

    private static String mergeCityAddress(String city, String address) {
        String c = city.trim();
        String a = address.trim();
        if (a.startsWith(c)) {
            return a;
        }
        return c + a;
    }

    private IllegalArgumentException geocodeFailureException(String json) throws Exception {
        JsonNode root = objectMapper.readTree(json);
        String info = root.path("info").asText("地理编码失败");
        String infocode = root.path("infocode").asText("");
        if (StringUtils.hasText(infocode)) {
            return new IllegalArgumentException("地理编码失败：" + info + "（infocode=" + infocode + "）");
        }
        return new IllegalArgumentException("地理编码失败：" + info);
    }

    /** 与 AllShopCollectorV5#getAllShops 相同的参数顺序与拼接方式（location 为经纬度串，无需 URL 编码）。 */
    private String fetchAroundPage(String location, int radius, int offset, int page) {
        String url = AROUND_URL + "?key=" + amapProperties.getWebServiceKey()
                + "&location=" + location
                + "&radius=" + radius
                + "&offset=" + offset
                + "&page=" + page
                + "&extensions=all"
                + "&output=json";
        String body = restTemplate.getForObject(URI.create(url), String.class);
        if (body == null) {
            throw new IllegalStateException("高德周边搜索返回空响应");
        }
        return body;
    }

    private static String text(JsonNode node, String field) {
        if (node == null || !node.has(field)) {
            return "";
        }
        return node.get(field).asText("");
    }

    /**
     * 高德 POI {@code photos} 为数组：元素可为对象 {@code { "url": "..." }} 或个别环境下为 URL 字符串。
     */
    private static List<String> extractPhotoUrls(JsonNode poi) {
        List<String> urls = new ArrayList<>();
        JsonNode photos = poi.path("photos");
        if (!photos.isArray()) {
            return urls;
        }
        for (JsonNode ph : photos) {
            if (ph == null || ph.isNull()) {
                continue;
            }
            if (ph.isTextual()) {
                String u = ph.asText().trim();
                if (StringUtils.hasText(u)) {
                    urls.add(u);
                    if (urls.size() >= MAX_PHOTO_URLS) {
                        break;
                    }
                }
                continue;
            }
            String u = ph.path("url").asText("").trim();
            if (StringUtils.hasText(u)) {
                urls.add(u);
            }
            if (urls.size() >= MAX_PHOTO_URLS) {
                break;
            }
        }
        return urls;
    }

    /**
     * 按 {@code contact} 与 {@code customer_management} 对齐：已存在则更新，不存在则插入。
     * 店名/地址/电话/三张照片/门店与销售默认值等见 {@link #applyMerchantDataToCustomer}；更新时保留主键、创建时间及录音/联系次数。
     */
    private int persistMerchantsAsCustomers(List<NearbyMerchantItem> items, String searchCity, String searchAddress) {
        int affected = 0;
        LocalDateTime now = LocalDateTime.now();
        for (NearbyMerchantItem item : items) {
            String contact = item.getTel();
            if (!StringUtils.hasText(contact)) {
                continue;
            }
            String contactTrim = contact.trim();
            try {
                LambdaQueryWrapper<CustomerManagement> w = new LambdaQueryWrapper<CustomerManagement>()
                        .eq(CustomerManagement::getContact, contactTrim)
                        .last("LIMIT 1");
                CustomerManagement existing = customerManagementService.getOne(w, false);
                if (existing == null) {
                    CustomerManagement row = new CustomerManagement();
                    row.setId(UUID.randomUUID().toString());
                    applyMerchantDataToCustomer(row, item, contactTrim, searchCity, searchAddress);
                    row.setRecordingCount(0);
                    row.setContactCount(0);
                    row.setCreateTime(now);
                    row.setUpdateTime(now);
                    if (customerManagementService.save(row)) {
                        affected++;
                    }
                } else {
                    applyMerchantDataToCustomer(existing, item, contactTrim, searchCity, searchAddress);
                    existing.setUpdateTime(now);
                    if (customerManagementService.updateById(existing)) {
                        affected++;
                    }
                }
            } catch (Exception e) {
                log.warn("写入 customer_management 失败, contact={}: {}", contactTrim, e.getMessage());
            }
        }
        return affected;
    }

    private static void applyMerchantDataToCustomer(CustomerManagement c, NearbyMerchantItem item, String contactTrim,
                                                   String searchCity, String searchAddress) {
        String name = item.getName() != null ? item.getName().trim() : "";
        c.setCustomerName(name.isEmpty() ? "高德POI" : name);
        c.setDetailedAddress(blankToNull(item.getAddress()));
        c.setContact(contactTrim);
        c.setRemark(remarkFromSearchParams(searchCity, searchAddress));
        List<String> urls = item.getPhotoUrls();
        if (urls != null && !urls.isEmpty()) {
            int n = Math.min(urls.size(), MAX_PHOTO_URLS);
            c.setFrontPhotoUrl(n > 0 ? urls.get(0) : null);
            c.setSidePhotoUrl(n > 1 ? urls.get(1) : null);
            c.setLifePhotoUrl(n > 2 ? urls.get(2) : null);
        } else {
            c.setFrontPhotoUrl(null);
            c.setSidePhotoUrl(null);
            c.setLifePhotoUrl(null);
        }
        c.setCustomerSource("AMAP_GaoDe");
        c.setDealershipId(DEFAULT_DEALERSHIP_ID);
        c.setDealershipName(DEFAULT_DEALERSHIP_NAME);
        c.setSalesId(DEFAULT_SALES_ID);
        c.setSalesName(DEFAULT_SALES_NAME);
        c.setSalesPhone(DEFAULT_SALES_PHONE);
        c.setStage(DEFAULT_STAGE);
    }

    /** 接口检索参数 CITY、ADDRESS 两段拼接写入 {@code remark} */
    private static String remarkFromSearchParams(String searchCity, String searchAddress) {
        String c = searchCity != null ? searchCity.trim() : "";
        String a = searchAddress != null ? searchAddress.trim() : "";
        if (!StringUtils.hasText(c) && !StringUtils.hasText(a)) {
            return null;
        }
        if (!StringUtils.hasText(c)) {
            return a;
        }
        if (!StringUtils.hasText(a)) {
            return c;
        }
        return c + " " + a;
    }

    private static String blankToNull(String s) {
        if (s == null) {
            return null;
        }
        String t = s.trim();
        return t.isEmpty() ? null : t;
    }

    /**
     * 参考 {@link com.rj.gaode AllShopCollectorV5  parseAndPrint}：每家店为「店名/地址/电话/行业/坐标… + ---------------------------」。
     */
    private static String buildDetailedReport(String city, String address, int radiusMeters, String centerLocation,
                                              String totalCount, List<NearbyMerchantItem> merchants) {
        StringBuilder sb = new StringBuilder();
        sb.append("===== 附近商家检索（输出格式参考 AllShopCollectorV5）=====\n");
        sb.append("说明：仅保留联系电话(tel)非空的 POI；高德 count 为接口原始命中总数。\n");
        sb.append("城市(city)：").append(city).append('\n');
        sb.append("地址(address)：").append(address).append('\n');
        sb.append("搜索半径(米)：").append(radiusMeters).append('\n');
        sb.append("中心经纬度(location)：").append(centerLocation).append('\n');
        sb.append("高德返回总条数(count)：").append(totalCount != null ? totalCount : "无").append('\n');
        sb.append("本响应保留条数（有电话）：").append(merchants.size()).append('\n');
        sb.append("===== 各商铺信息 =====\n");
        int n = 1;
        for (NearbyMerchantItem m : merchants) {
            sb.append("\n【第 ").append(n++).append(" 家】\n");
            sb.append(m.toDetailBlock()).append('\n');
        }
        if (merchants.isEmpty()) {
            sb.append("\n（无 POI 结果）\n");
        }
        return sb.toString();
    }
}
