package com.gxwebsoft.cms.controller;

import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.gxwebsoft.cms.entity.CmsDesign;
import com.gxwebsoft.cms.entity.CmsModel;
import com.gxwebsoft.cms.param.CmsNavigationImportParam;
import com.gxwebsoft.cms.service.CmsDesignService;
import com.gxwebsoft.cms.service.CmsModelService;
import com.gxwebsoft.common.core.annotation.OperationLog;
import com.gxwebsoft.common.core.utils.CommonUtil;
import com.gxwebsoft.common.core.utils.RedisUtil;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.cms.service.CmsNavigationService;
import com.gxwebsoft.cms.entity.CmsNavigation;
import com.gxwebsoft.cms.param.CmsNavigationParam;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.exception.BusinessException;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.common.system.service.UserService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.Map;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;


import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 网站导航记录表控制器
 *
 * @author 科技小王子
 * @since 2024-09-10 20:47:57
 */
@Tag(name = "网站导航记录表管理")
@RestController
@RequestMapping("/api/cms/cms-navigation")
public class CmsNavigationController extends BaseController {
  @Resource
  private CmsNavigationService cmsNavigationService;
  @Resource
  private CmsModelService cmsModelService;
  @Resource
  private CmsDesignService cmsDesignService;
  @Resource
  private UserService userService;
  @Resource
  private RedisUtil redisUtil;


  private static final String SITE_INFO_KEY_PREFIX = "SiteInfo:";

  @Operation(summary = "分页查询网站导航记录表")
  @GetMapping("/page")
  public ApiResult<PageResult<CmsNavigation>> page(CmsNavigationParam param) {
    // 使用关联查询
    return success(cmsNavigationService.pageRel(param));
  }

  @Operation(summary = "查询全部网站导航记录表")
  @GetMapping()
  public ApiResult<List<CmsNavigation>> list(CmsNavigationParam param) {
    // 使用关联查询
    return success(cmsNavigationService.listRel(param));
  }

  @Operation(summary = "根据id查询网站导航记录表")
  @GetMapping("/{id}")
  public ApiResult<CmsNavigation> get(@PathVariable("id") Integer id) {
    // 使用关联查询
    return success(cmsNavigationService.getByIdRel(id));
  }

  @Operation(summary = "根据code查询网站导航记录表")
  @GetMapping("/getByCode/{code}")
  public ApiResult<CmsNavigation> getByCode(@PathVariable("code") String code) {
    // 使用关联查询
    return success(cmsNavigationService.getByIdRelByCodeRel(code));
  }

  @PreAuthorize("hasAuthority('cms:cmsNavigation:save')")
  @Operation(summary = "添加网站导航记录表")
  @PostMapping()
  public ApiResult<?> save(@RequestBody CmsNavigation cmsNavigation) {
    // 记录当前登录用户id
    User loginUser = getLoginUser();
    if (loginUser != null) {
      cmsNavigation.setUserId(loginUser.getUserId());
      cmsNavigation.setTenantId(loginUser.getTenantId());
    }
    // 去除前面空格
    cmsNavigation.setTitle(StrUtil.trimStart(cmsNavigation.getTitle()));
    // code 规范化：trim + 转小写（唯一性以规范化后的值为准）
    if (cmsNavigation.getCode() != null) {
      cmsNavigation.setCode(cmsNavigation.getCode().trim().toLowerCase());
    }
    // 单页模型绑定唯一性校验：同一租户下同一个单页只能绑定一个栏目
    checkPageBindingUnique(cmsNavigation);
    // 栏目 code 租户内唯一性校验（code 为空/空串时不校验）
    checkCodeUnique(cmsNavigation);
    if (cmsNavigationService.save(cmsNavigation)) {
      // 添加成功事务处理
      cmsNavigationService.saveAsync(cmsNavigation);
      redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(getTenantId().toString()));
      return success("添加成功");
    }
    return fail("添加失败");
  }

  @PreAuthorize("hasAuthority('cms:cmsNavigation:update')")
  @Operation(summary = "修改网站导航记录表")
  @PutMapping()
  public ApiResult<?> update(@RequestBody CmsNavigation cmsNavigation) {
    // code 规范化：trim + 转小写（唯一性以规范化后的值为准）
    if (cmsNavigation.getCode() != null) {
      cmsNavigation.setCode(cmsNavigation.getCode().trim().toLowerCase());
    }
    // 单页模型绑定唯一性校验：同一租户下同一个单页只能绑定一个栏目
    checkPageBindingUnique(cmsNavigation);
    // 栏目 code 租户内唯一性校验（code 为空/空串时不校验）
    checkCodeUnique(cmsNavigation);
    // 顶级栏目（parentId=0）的显示位置（top/bottom）发生变更时，
    // 递归同步所有子级（含孙级）的 top/bottom，保持层级一致。
    if (cmsNavigation.getParentId() != null && cmsNavigation.getParentId() == 0
        && cmsNavigation.getNavigationId() != null
        && (cmsNavigation.getTop() != null || cmsNavigation.getBottom() != null)) {
      CmsNavigation oldNav = cmsNavigationService.getById(cmsNavigation.getNavigationId());
      if (oldNav != null) {
        boolean topChanged = cmsNavigation.getTop() != null && !cmsNavigation.getTop().equals(oldNav.getTop());
        boolean bottomChanged = cmsNavigation.getBottom() != null && !cmsNavigation.getBottom().equals(oldNav.getBottom());
        if (topChanged || bottomChanged) {
          cmsNavigationService.syncChildrenPosition(
              cmsNavigation.getNavigationId(),
              cmsNavigation.getTop() != null ? cmsNavigation.getTop() : oldNav.getTop(),
              cmsNavigation.getBottom() != null ? cmsNavigation.getBottom() : oldNav.getBottom(),
              getTenantId());
        }
      }
    }
    if (cmsNavigationService.updateById(cmsNavigation)) {
      // 修改成功事务处理
      cmsNavigationService.saveAsync(cmsNavigation);
      redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(getTenantId().toString()));
      return success("修改成功");
    }
    return fail("修改失败");
  }

  @PreAuthorize("hasAuthority('cms:cmsNavigation:remove')")
  @Operation(summary = "删除网站导航记录表")
  @DeleteMapping("/{id}")
  public ApiResult<?> remove(@PathVariable("id") Integer id) {
    if (cmsNavigationService.removeById(id)) {
      redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(getTenantId().toString()));
      return success("删除成功");
    }
    return fail("删除失败");
  }

  @PreAuthorize("hasAuthority('cms:cmsNavigation:update')")
  /**
   * 校验栏目 code 租户内唯一：同一租户下 code 不可重复（code 为空/空串时不校验）。
   * 与单页绑定唯一性校验并列，避免触发 uk_navigation_tenant_code 唯一索引导致用户看到"操作失败"。
   */
  private void checkCodeUnique(CmsNavigation cmsNavigation) {
    String code = cmsNavigation.getCode();
    if (code == null || code.trim().isEmpty()) {
      return;
    }
    LambdaQueryWrapper<CmsNavigation> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(CmsNavigation::getTenantId, getTenantId())
        .eq(CmsNavigation::getCode, code.trim())
        .eq(CmsNavigation::getDeleted, 0);
    if (cmsNavigation.getNavigationId() != null) {
      wrapper.ne(CmsNavigation::getNavigationId, cmsNavigation.getNavigationId());
    }
    if (cmsNavigationService.count(wrapper) > 0) {
      throw new BusinessException("栏目 code「" + code.trim() + "」已存在，请更换");
    }
  }

  @Operation(summary = "修改栏目状态")
  @PutMapping("/status")
  public ApiResult<?> updateStatus(@RequestBody Map<String, Object> body) {
    Object menuIdObj = body.get("menuId");
    Object statusObj = body.get("status");
    if (menuIdObj == null) {
      return fail("缺少 menuId");
    }
    CmsNavigation entity = new CmsNavigation();
    entity.setNavigationId(((Number) menuIdObj).intValue());
    if (statusObj != null) {
      entity.setStatus(((Number) statusObj).intValue());
    }
    if (cmsNavigationService.updateById(entity)) {
      redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(getTenantId().toString()));
      return success("修改成功");
    }
    return fail("修改失败");
  }

  @PreAuthorize("hasAuthority('cms:cmsNavigation:save')")
  @Operation(summary = "批量添加网站导航记录表")
  @PostMapping("/batch")
  public ApiResult<?> saveBatch(@RequestBody List<CmsNavigation> list) {
    if (cmsNavigationService.saveBatch(list)) {
      redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(getTenantId().toString()));
      return success("添加成功");
    }
    return fail("添加失败");
  }

  @PreAuthorize("hasAuthority('cms:cmsNavigation:update')")
  @Operation(summary = "批量修改网站导航记录表")
  @PutMapping("/batch")
  public ApiResult<?> removeBatch(@RequestBody BatchParam<CmsNavigation> batchParam) {
    if (batchParam.update(cmsNavigationService, "navigation_id")) {
      redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(getTenantId().toString()));
      return success("修改成功");
    }
    return fail("修改失败");
  }

  @PreAuthorize("hasAuthority('cms:cmsNavigation:remove')")
  @Operation(summary = "批量删除网站导航记录表")
  @DeleteMapping("/batch")
  public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
    if (cmsNavigationService.removeByIds(ids)) {
      redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(getTenantId().toString()));
      return success("删除成功");
    }
    return fail("删除失败");
  }

  /**
   * excel批量导入网站导航
   */
  @PreAuthorize("hasAuthority('cms:cmsNavigation:save')")
  @OperationLog
  @Operation(summary = "批量导入网站导航")
  @Transactional(rollbackFor = {Exception.class})
  @PostMapping("/import")
  public ApiResult<?> importBatch(@RequestParam("file") MultipartFile file) {
    ImportParams importParams = new ImportParams();
    try {
      User loginUser = getLoginUser();
      if (loginUser == null) {
        return fail("请先登录");
      }
      Integer currentUserId = loginUser.getUserId();
      Integer currentTenantId = loginUser.getTenantId();

      // 1) 清理当前租户的历史数据：先清理 deleted=1，再把 deleted=0 标记为 deleted=1
      List<CmsNavigation> undeleted = cmsNavigationService.list(new LambdaQueryWrapper<CmsNavigation>()
          .eq(CmsNavigation::getTenantId, currentTenantId)
          .eq(CmsNavigation::getDeleted, 0));
      cmsNavigationService.remove(new LambdaQueryWrapper<CmsNavigation>()
          .eq(CmsNavigation::getTenantId, currentTenantId)
          .eq(CmsNavigation::getDeleted, 1));
      if (!CollectionUtils.isEmpty(undeleted)) {
        LambdaUpdateWrapper<CmsNavigation> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(CmsNavigation::getTenantId, currentTenantId);
        updateWrapper.eq(CmsNavigation::getDeleted, 0);
        updateWrapper.set(CmsNavigation::getDeleted, 1);
        cmsNavigationService.update(updateWrapper);
      }

      // 2) 读取Excel
      List<CmsNavigationImportParam> list = ExcelImportUtil.importExcel(
          file.getInputStream(), CmsNavigationImportParam.class, importParams);
      if (CollectionUtils.isEmpty(list)) {
        return fail("未读取到数据，请确认模板表头与示例格式一致");
      }

      // 3) 先全部落库（先按根节点保存），再尝试按“导入文件的 parentId -> 导入文件的 navigationId”映射回填层级。
      // 这样即使 parentId 无法匹配（导入文件缺少导航ID/父节点缺失），也能把数据全部导入，无法还原层级的作为根节点处理。
      Map<Integer, Integer> newIdByOldId = new HashMap<>();
      List<Integer> newIds = new ArrayList<>(list.size());
      List<Integer> oldIds = new ArrayList<>(list.size());
      List<Integer> oldParentIds = new ArrayList<>(list.size());

      for (CmsNavigationImportParam param : list) {
        CmsNavigation nav = convertToNavigation(param, currentUserId, currentTenantId);
        nav.setParentId(0);
        cmsNavigationService.save(nav);
        cmsNavigationService.saveAsync(nav);

        Integer oldId = param.getNavigationId();
        if (oldId != null) {
          newIdByOldId.put(oldId, nav.getNavigationId());
        }
        newIds.add(nav.getNavigationId());
        oldIds.add(oldId);
        oldParentIds.add(param.getParentId() != null ? param.getParentId() : 0);
      }

      int orphanCount = 0;
      int restoredCount = 0;
      for (int i = 0; i < newIds.size(); i++) {
        Integer oldParentId = oldParentIds.get(i);
        if (oldParentId == null || oldParentId == 0) {
          continue;
        }
        Integer oldId = oldIds.get(i);
        Integer newId = newIds.get(i);
        Integer newParentId = newIdByOldId.get(oldParentId);
        // 无法匹配父节点（或出现自引用）就当作孤儿节点，保持根节点
        if (newParentId == null || (oldId != null && oldParentId.equals(oldId)) || (newId != null && newParentId.equals(newId))) {
          orphanCount++;
          continue;
        }
        cmsNavigationService.update(new LambdaUpdateWrapper<CmsNavigation>()
          .eq(CmsNavigation::getNavigationId, newId)
          .set(CmsNavigation::getParentId, newParentId));
        restoredCount++;
      }

      redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(currentTenantId.toString()));
      return success("成功导入" + list.size() + "条，恢复层级" + restoredCount + "条，无法还原层级的孤儿节点" + orphanCount + "条（已作为根节点导入）");
    } catch (Exception e) {
      e.printStackTrace();
      return fail("导入失败: " + e.getMessage());
    }
  }

  /**
   * 校验单页绑定唯一性：同一租户下同一个单页（pageId）只能绑定一个栏目，
   * 避免触发 cms_navigation.uk_navigation_tenant_page 唯一索引导致用户看到"操作失败"。
   */
  private void checkPageBindingUnique(CmsNavigation cmsNavigation) {
    if (!"page".equals(cmsNavigation.getModel()) || cmsNavigation.getPageId() == null) {
      return;
    }
    LambdaQueryWrapper<CmsNavigation> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(CmsNavigation::getTenantId, getTenantId())
        .eq(CmsNavigation::getPageId, cmsNavigation.getPageId())
        .eq(CmsNavigation::getDeleted, 0);
    if (cmsNavigation.getNavigationId() != null) {
      wrapper.ne(CmsNavigation::getNavigationId, cmsNavigation.getNavigationId());
    }
    if (cmsNavigationService.count(wrapper) > 0) {
      throw new BusinessException("该单页已被其他栏目绑定，请先解绑后再绑定");
    }
  }

  /**
   * 递归创建子级导航
   */
  private void createChildNavigations(Map<Integer, List<CmsNavigationImportParam>> navGroups,
                                      Map<Integer, CmsNavigation> tempIdMapping,
                                      Integer originalParentId,
                                      Integer defaultUserId,
                                      Integer defaultTenantId) {
    if (originalParentId == null || originalParentId == 0) {
      return;
    }
    List<CmsNavigationImportParam> children = navGroups.get(originalParentId);
    if (CollectionUtils.isEmpty(children)) {
      return;
    }
    CmsNavigation parent = tempIdMapping.get(originalParentId);
    if (parent == null || parent.getNavigationId() == null) {
      return;
    }
    Integer newParentId = parent.getNavigationId();
    for (CmsNavigationImportParam param : children) {
      CmsNavigation nav = convertToNavigation(param, defaultUserId, defaultTenantId);
      nav.setParentId(newParentId);
      cmsNavigationService.save(nav);
      cmsNavigationService.saveAsync(nav);
      if (param.getNavigationId() != null) {
        tempIdMapping.put(param.getNavigationId(), nav);
      }
      createChildNavigations(navGroups, tempIdMapping, param.getNavigationId(), defaultUserId, defaultTenantId);
    }
  }

  private CmsNavigation convertToNavigation(CmsNavigationImportParam param, Integer defaultUserId, Integer defaultTenantId) {
    CmsNavigation nav = new CmsNavigation();
    nav.setType(param.getType());
    nav.setTitle(StrUtil.trimStart(param.getTitle()));
    nav.setParentId(param.getParentId() != null ? param.getParentId() : 0);
    // saveAsync 依赖 model 生成路由/页面；缺省按 page 处理
    nav.setModel(StrUtil.isBlank(param.getModel()) ? "page" : param.getModel());
    nav.setCode(param.getCode());
    nav.setPath(param.getPath());
    nav.setUrl(param.getUrl());
    nav.setComponent(param.getComponent());
    nav.setTarget(param.getTarget());
    nav.setIcon(param.getIcon());
    nav.setColor(param.getColor());
    nav.setHide(param.getHide());
    nav.setPermission(param.getPermission());
    nav.setPassword(param.getPassword());
    nav.setPosition(param.getPosition());
    nav.setTop(param.getTop());
    nav.setBottom(param.getBottom());
    nav.setActive(param.getActive());
    nav.setMeta(param.getMeta());
    nav.setStyle(param.getStyle());
    nav.setModelName(param.getModelName());
    nav.setPageId(param.getPageId());
    nav.setItemId(param.getItemId());
    nav.setIsMpWeixin(param.getIsMpWeixin());
    nav.setGutter(param.getGutter());
    nav.setSpan(param.getSpan());
    nav.setReadNum(param.getReadNum());
    nav.setMerchantId(param.getMerchantId());
    nav.setLang(param.getLang());
    nav.setHome(param.getHome());
    nav.setRecommend(param.getRecommend());
    nav.setSortNumber(param.getSortNumber() != null ? param.getSortNumber() : 0);
    nav.setComments(param.getComments());
    nav.setStatus(param.getStatus() != null ? param.getStatus() : 0);

    // 导入时强制落到当前登录用户/租户，避免“导出其他租户 -> 导入”写回原租户导致当前租户数据不全。
    nav.setUserId(defaultUserId);
    nav.setTenantId(defaultTenantId);
    nav.setDeleted(0);
    return nav;
  }

  @Operation(summary = "获取树形结构的网站导航数据")
  @GetMapping("/tree")
  public ApiResult<List<CmsNavigation>> tree(CmsNavigationParam param) {
    // 注意：不要再强制 param.setHide(0)。
    // 历史 bug：管理员把栏目改成"隐藏"后，再调本接口会因为 hide=1 被过滤，导致栏目从管理列表里消失，
    // 看起来"保存失败/没生效"，但实际 updateById 已成功。
    // 后台管理场景必须能看到所有栏目（包括隐藏的）；若以后公开站需要只拉显示中的栏目，
    // 应由调用方显式传 hide=0，而不是在 controller 写死。
    final List<CmsNavigation> navigations = cmsNavigationService.listRel(param);
    return success(CommonUtil.toTreeData(navigations, 0, CmsNavigation::getParentId, CmsNavigation::getNavigationId, CmsNavigation::setChildren));
  }

  @Operation(summary = "根据path获取导航")
  @GetMapping("/getNavigationByPath")
  public ApiResult<CmsNavigation> getNavigationByPath(CmsNavigationParam param) {
    final CmsNavigation navigation = cmsNavigationService.getOne(new LambdaUpdateWrapper<CmsNavigation>().eq(CmsNavigation::getModel, param.getModel()).last("limit 1"));
    if (ObjectUtil.isNotEmpty(navigation)) {
      // 页面元素
      final CmsDesign design = cmsDesignService.getOne(new LambdaUpdateWrapper<CmsDesign>().eq(CmsDesign::getCategoryId, navigation.getNavigationId()).last("limit 1"));
      // 模型信息
      final CmsModel model = cmsModelService.getOne(new LambdaQueryWrapper<CmsModel>().eq(CmsModel::getModel, navigation.getModel()).last("limit 1"));
      navigation.setBanner(model.getBanner());
      // 上级导航
      if (!navigation.getParentId().equals(0)) {
        final CmsNavigation parent = cmsNavigationService.getById(navigation.getParentId());
        navigation.setParentPath(parent.getPath());
        navigation.setParentName(parent.getTitle());
      }
      // 页面信息
      navigation.setDesign(design);
      // 页面布局
      if (ObjectUtil.isNotEmpty(design)) {
        navigation.setLayout(design.getLayout());
      }
    }
    return success(navigation);
  }

  @Operation(summary = "密码校验")
  @GetMapping("/checkNavigationPassword")
  public ApiResult<?> checkNavigationPassword(CmsNavigationParam param) {
    if (!userService.comparePassword(param.getPassword(), param.getPassword2())) {
      return fail("密码不正确");
    }
    return success("密码正确");
  }

}
