package com.gxwebsoft.led.service;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gxwebsoft.common.core.exception.BusinessException;
import com.gxwebsoft.common.core.utils.JSONUtil;
import com.gxwebsoft.common.core.utils.RedisUtil;
import com.gxwebsoft.led.config.BmeApiProperties;
import com.gxwebsoft.led.model.BmeToken;
import com.gxwebsoft.led.param.BmeNumberSourcesParam;
import com.gxwebsoft.led.param.BmeStopReplaceParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 排班接口对接服务。
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class LedScheduleService {

  private static final String TOKEN_CACHE_KEY = "led:bme:auth";
  private static final long TOKEN_EXPIRE_MARGIN_SECONDS = 60L;

  private final BmeApiProperties properties;
  private final RedisUtil redisUtil;

  public JSONObject searchStopAndReplace(BmeStopReplaceParam param) {
    BmeStopReplaceParam requestParam = fillStopReplaceDefaults(param);
    BmeToken token = ensureToken();
    String body = buildStopReplaceBody(requestParam);
    return doSignedPost("/cgi-bin/bme-app-register/outpatient/registered/searchDoctorsStopAndReplaceInfo", body, token);
  }

  public JSONObject searchNumberSources(BmeNumberSourcesParam param) {
    BmeNumberSourcesParam requestParam = fillNumberSourcesDefaults(param);
    BmeToken token = ensureToken();
    String body = buildNumberSourcesBody(requestParam);
    return doSignedPost("/cgi-bin/bme-app-register/outpatient/registered/searchDoctorsNumberSources", body, token);
  }

  private JSONObject doSignedPost(String path, String body, BmeToken token) {
    if (StrUtil.isBlank(body)) {
      throw new BusinessException("请求体为空");
    }
    ensureConfig();
    long timestamp = System.currentTimeMillis() / 1000;
    String nonce = RandomUtil.randomString(16);
    String signStr = body + "|" + token.getAccessToken() + "|" + token.getRefreshToken() + "|" + timestamp + "|" + nonce;
    String sig = DigestUtil.md5Hex(signStr);
    HttpResponse response = HttpRequest.post(buildUrl(path))
      .header("sig", sig)
      .header("timestamp", String.valueOf(timestamp))
      .header("nonce", nonce)
      .header("access-token", token.getAccessToken())
      .contentType("application/json")
      .body(body)
      .timeout(properties.getTimeoutMs())
      .execute();
    if (response.getStatus() != 200) {
      throw new BusinessException("调用中台接口失败，HTTP状态码：" + response.getStatus());
    }
    String respBody = response.body();
    JSONObject respJson;
    try {
      respJson = JSON.parseObject(respBody);
    } catch (Exception e) {
      throw new BusinessException("解析中台响应失败");
    }
    if (respJson == null || !"0".equals(respJson.getString("code"))) {
      String message = respJson != null ? respJson.getString("msg") : "未知错误";
      throw new BusinessException("中台接口调用失败：" + message);
    }
    return respJson;
  }

  private BmeStopReplaceParam fillStopReplaceDefaults(BmeStopReplaceParam param) {
    if (param == null) {
      throw new BusinessException("请求参数不能为空");
    }
    if (StrUtil.isBlank(param.getStartDate()) || StrUtil.isBlank(param.getEndDate())) {
      throw new BusinessException("开始日期和结束日期必填");
    }
    if (StrUtil.isBlank(param.getMechanismId())) {
      param.setMechanismId(properties.getMechanismId());
    }
    if (StrUtil.isBlank(param.getExtUserID())) {
      param.setExtUserID(properties.getDefaultExtUserId());
    }
    if (StrUtil.isBlank(param.getHospitalId())) {
      param.setHospitalId(properties.getDefaultHospitalId());
    }
    if (StrUtil.isBlank(param.getTradeCode())) {
      param.setTradeCode("10017");
    }
    if (StrUtil.hasBlank(param.getMechanismId(), param.getExtUserID(), param.getTradeCode())) {
      throw new BusinessException("机构ID、操作员代码、交易代码必填");
    }
    return param;
  }

  private BmeNumberSourcesParam fillNumberSourcesDefaults(BmeNumberSourcesParam param) {
    if (param == null) {
      throw new BusinessException("请求参数不能为空");
    }
    if (StrUtil.isBlank(param.getStartDate()) || StrUtil.isBlank(param.getEndDate())) {
      throw new BusinessException("开始日期和结束日期必填");
    }
    if (StrUtil.isBlank(param.getMechanismId())) {
      param.setMechanismId(properties.getMechanismId());
    }
    if (StrUtil.isBlank(param.getExtUserID())) {
      param.setExtUserID(properties.getDefaultExtUserId());
    }
    if (StrUtil.isBlank(param.getHospitalId())) {
      param.setHospitalId(properties.getDefaultHospitalId());
    }
    if (StrUtil.isBlank(param.getTradeCode())) {
      param.setTradeCode("10018");
    }
    if (StrUtil.hasBlank(param.getMechanismId(), param.getExtUserID(), param.getTradeCode())) {
      throw new BusinessException("机构ID、操作员代码、交易代码必填");
    }
    return param;
  }

  private String buildStopReplaceBody(BmeStopReplaceParam param) {
    Map<String, Object> body = new LinkedHashMap<>();
    body.put("mechanismId", param.getMechanismId());
    body.put("tradeCode", param.getTradeCode());
    body.put("startDate", param.getStartDate());
    body.put("endDate", param.getEndDate());
    putIfNotBlank(body, "deptCode", param.getDeptCode());
    putIfNotBlank(body, "userCode", param.getUserCode());
    putIfNotBlank(body, "hospitalId", param.getHospitalId());
    body.put("extUserID", param.getExtUserID());
    return JSONUtil.toJSONString(body);
  }

  private String buildNumberSourcesBody(BmeNumberSourcesParam param) {
    Map<String, Object> body = new LinkedHashMap<>();
    body.put("mechanismId", param.getMechanismId());
    body.put("tradeCode", param.getTradeCode());
    body.put("startDate", param.getStartDate());
    body.put("endDate", param.getEndDate());
    putIfNotBlank(body, "deptCode", param.getDeptCode());
    putIfNotBlank(body, "userCode", param.getUserCode());
    putIfNotBlank(body, "aSTimeRange", param.getASTimeRange());
    putIfNotBlank(body, "hospitalId", param.getHospitalId());
    body.put("extUserID", param.getExtUserID());
    return JSONUtil.toJSONString(body);
  }

  private void putIfNotBlank(Map<String, Object> body, String key, String value) {
    if (StrUtil.isNotBlank(value)) {
      body.put(key, value);
    }
  }

  private BmeToken ensureToken() {
    ensureConfig();
    long now = System.currentTimeMillis() / 1000;
    BmeToken cached = redisUtil.get(TOKEN_CACHE_KEY, BmeToken.class);
    if (cached != null && cached.getExpireAt() > now + TOKEN_EXPIRE_MARGIN_SECONDS) {
      return cached;
    }
    if (cached != null && StrUtil.isNotBlank(cached.getRefreshToken())) {
      try {
        return refreshToken(cached.getRefreshToken());
      } catch (Exception e) {
        log.warn("刷新 access_token 失败，尝试重新申请: {}", e.getMessage());
      }
    }
    return fetchToken();
  }

  private BmeToken fetchToken() {
    long timestamp = System.currentTimeMillis() / 1000;
    String nonce = IdUtil.fastSimpleUUID();
    String grantType = "secret";
    Map<String, Object> form = new HashMap<>();
    form.put("appid", properties.getAppid());
    form.put("grant_type", grantType);
    form.put("secret_key", properties.getSecretKey());
    form.put("timestamp", timestamp);
    form.put("nonce", nonce);
    String signStr = properties.getAppid() + "|" + properties.getSecretKey() + "|" + grantType + "|" + timestamp + "|" + nonce;
    form.put("sign", DigestUtil.md5Hex(signStr));

    HttpResponse response = HttpRequest.get(buildUrl("/cgi-bin/bme-auth/auth/access_token"))
      .form(form)
      .timeout(properties.getTimeoutMs())
      .execute();
    return handleTokenResponse(response);
  }

  private BmeToken refreshToken(String refreshToken) {
    long timestamp = System.currentTimeMillis() / 1000;
    String nonce = IdUtil.fastSimpleUUID();
    String grantType = "refresh_token";
    Map<String, Object> form = new HashMap<>();
    form.put("appid", properties.getAppid());
    form.put("grant_type", grantType);
    form.put("refresh_token", refreshToken);
    form.put("timestamp", timestamp);
    form.put("nonce", nonce);
    String signStr = properties.getAppid() + "|" + properties.getSecretKey() + "|" + refreshToken + "|" + grantType + "|" + timestamp + "|" + nonce;
    form.put("sign", DigestUtil.md5Hex(signStr));
    HttpResponse response = HttpRequest.get(buildUrl("/cgi-bin/bme-auth/auth/refresh_token"))
      .form(form)
      .timeout(properties.getTimeoutMs())
      .execute();
    return handleTokenResponse(response);
  }

  private BmeToken handleTokenResponse(HttpResponse response) {
    if (response.getStatus() != 200) {
      throw new BusinessException("获取中台token失败，HTTP状态码：" + response.getStatus());
    }
    JSONObject resp;
    try {
      resp = JSON.parseObject(response.body());
    } catch (Exception e) {
      throw new BusinessException("解析中台token响应失败");
    }
    if (resp == null || !"0".equals(resp.getString("code"))) {
      String message = resp != null ? resp.getString("msg") : "未知错误";
      throw new BusinessException("获取中台token失败：" + message);
    }
    JSONObject data = resp.getJSONObject("data");
    if (data == null) {
      throw new BusinessException("中台token响应缺少数据");
    }
    String accessToken = data.getString("access_token");
    String refreshToken = data.getString("refresh_token");
    int expiresIn = data.getIntValue("expires_in");
    if (StrUtil.hasBlank(accessToken, refreshToken) || expiresIn <= 0) {
      throw new BusinessException("中台token响应不完整");
    }
    long expireAt = System.currentTimeMillis() / 1000 + expiresIn - TOKEN_EXPIRE_MARGIN_SECONDS;
    BmeToken token = new BmeToken();
    token.setAccessToken(accessToken);
    token.setRefreshToken(refreshToken);
    token.setExpireAt(expireAt);
    // refresh_token有效期5天，这里缓存5天
    redisUtil.set(TOKEN_CACHE_KEY, token, Duration.ofDays(5).getSeconds(), TimeUnit.SECONDS);
    System.out.println("token = " + token);
    return token;
  }

  private void ensureConfig() {
    if (StrUtil.hasBlank(properties.getBaseUrl(), properties.getAppid(), properties.getSecretKey())) {
      throw new BusinessException("排班接口配置缺失，请检查 led.bme 配置");
    }
  }

  private String buildUrl(String path) {
    String base = properties.getBaseUrl();
    if (StrUtil.isBlank(base)) {
      throw new BusinessException("排班接口基础地址未配置");
    }
    if (base.endsWith("/")) {
      base = base.substring(0, base.length() - 1);
    }
    if (!path.startsWith("/")) {
      path = "/" + path;
    }
    // 兼容 baseUrl 已包含 /cgi-bin 的场景，避免拼接成 /cgi-bin/cgi-bin
    if (base.endsWith("/cgi-bin") && path.startsWith("/cgi-bin/")) {
      path = path.substring("/cgi-bin".length());
    }
    return base + path;
  }
}
