package cn.nianxx.thhotel.platform.navigation.service.impl;

import cn.nianxx.thhotel.platform.access.common.enums.PlatformPermissionStatus;
import cn.nianxx.thhotel.platform.access.repository.PlatformAccessRepository;
import cn.nianxx.thhotel.platform.audit.common.dto.PlatformAdminAuditLogDraft;
import cn.nianxx.thhotel.platform.audit.service.PlatformAdminAuditLogService;
import cn.nianxx.thhotel.platform.common.dto.PlatformPageSnapshot;
import cn.nianxx.thhotel.platform.common.exception.AdminOperationException;
import cn.nianxx.thhotel.platform.common.result.PlatformPageResult;
import cn.nianxx.thhotel.platform.common.result.PlatformPaginationResult;
import cn.nianxx.thhotel.platform.common.time.UtcTimeFormatter;
import cn.nianxx.thhotel.platform.navigation.common.enums.PlatformMenuStatus;
import cn.nianxx.thhotel.platform.navigation.common.request.AdminMenuCreateRequest;
import cn.nianxx.thhotel.platform.navigation.common.request.AdminMenuUpdateRequest;
import cn.nianxx.thhotel.platform.navigation.common.result.AdminMenuResult;
import cn.nianxx.thhotel.platform.navigation.domain.PlatformMenuEntity;
import cn.nianxx.thhotel.platform.navigation.repository.PlatformNavigationRepository;
import cn.nianxx.thhotel.platform.navigation.service.AdminMenuManagementService;
import cn.nianxx.thhotel.platform.security.common.dto.AuthenticatedUserContext;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Locale;
import java.util.Set;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 管理后台菜单管理服务实现。菜单可见性只控制入口，不替代后端接口鉴权。
 */
@Service
public class AdminMenuManagementServiceImpl implements AdminMenuManagementService {

    private static final int DEFAULT_PAGE_NUM = 1;
    private static final int DEFAULT_PAGE_SIZE = 20;
    private static final int MAX_PAGE_SIZE = 100;
    private static final Set<String> KNOWN_ROUTES = Set.of(
            "/reservation/orders",
            "/reservation/tasks",
            "/debug/eml-superagent",
            "/source-messages",
            "/system",
            "/system/users",
            "/system/roles",
            "/system/menus",
            "/system/hotels",
            "/system/audits");

    private final PlatformNavigationRepository navigationRepository;
    private final PlatformAccessRepository accessRepository;
    private final PlatformAdminAuditLogService auditLogService;
    private final ObjectMapper objectMapper;

    /**
     * 注入菜单仓储。
     */
    public AdminMenuManagementServiceImpl(
            PlatformNavigationRepository navigationRepository,
            PlatformAccessRepository accessRepository,
            PlatformAdminAuditLogService auditLogService,
            ObjectMapper objectMapper) {
        this.navigationRepository = navigationRepository;
        this.accessRepository = accessRepository;
        this.auditLogService = auditLogService;
        this.objectMapper = objectMapper;
    }

    /**
     * 分页查询菜单列表。
     */
    @Override
    public PlatformPageResult<AdminMenuResult> queryMenus(
            String keyword,
            String menuStatus,
            Integer pageNum,
            Integer pageSize) {
        int normalizedPageNum = normalizePageNum(pageNum);
        int normalizedPageSize = normalizePageSize(pageSize);
        PlatformPageSnapshot<PlatformMenuEntity> page = navigationRepository.queryMenus(
                trimToNull(keyword),
                trimToNull(menuStatus),
                normalizedPageNum,
                normalizedPageSize);
        return new PlatformPageResult<>(
                page.items().stream().map(this::toResult).toList(),
                new PlatformPaginationResult(page.pageNum(), page.pageSize(), page.total()));
    }

    /**
     * 查询菜单详情。
     */
    @Override
    public AdminMenuResult getMenu(String menuId) {
        Long id = parseId(menuId, "菜单 ID 不合法。");
        return navigationRepository.findMenuById(id)
                .map(this::toResult)
                .orElseThrow(() -> notFound("菜单不存在。"));
    }

    /**
     * 新增菜单定义。菜单代码作为稳定业务键，一旦创建不提供修改入口。
     */
    @Override
    @Transactional
    public AdminMenuResult createMenu(AdminMenuCreateRequest request, AuthenticatedUserContext actor) {
        if (request == null) {
            throw invalidRequest("菜单创建请求不能为空。");
        }
        String menuCode = requireCode(request.menuCode(), "菜单代码不能为空。");
        if (navigationRepository.findMenuByCode(menuCode).isPresent()) {
            throw conflict("菜单代码已存在。");
        }
        PlatformMenuEntity menu = new PlatformMenuEntity();
        applyMenuFields(menu, request.parentId(), request.menuName(), request.menuType(), request.routePath(),
                request.componentKey(), request.iconKey(), request.permissionCode(), request.sortOrder(),
                request.visible(), normalizeStatus(request.menuStatus(), PlatformMenuStatus.DISABLED.name()), true);
        menu.setMenuCode(menuCode);
        menu.setCreatedAt(nowUtc());
        menu.setUpdatedAt(nowUtc());
        navigationRepository.insertMenu(menu);
        AdminMenuResult after = getMenu(stringId(menu.getId()));
        audit(actor, "PLATFORM_MENU", stringId(menu.getId()), "CREATE_MENU", null, after);
        return after;
    }

    /**
     * 编辑菜单定义。菜单代码不允许修改，避免历史审计和后端菜单矩阵失去稳定键。
     */
    @Override
    @Transactional
    public AdminMenuResult updateMenu(String menuId, AdminMenuUpdateRequest request, AuthenticatedUserContext actor) {
        if (request == null) {
            throw invalidRequest("菜单更新请求不能为空。");
        }
        Long id = parseId(menuId, "菜单 ID 不合法。");
        PlatformMenuEntity menu = navigationRepository.findMenuById(id)
                .orElseThrow(() -> notFound("菜单不存在。"));
        AdminMenuResult before = toResult(menu);
        applyMenuFields(menu, request.parentId(), request.menuName(), request.menuType(), request.routePath(),
                request.componentKey(), request.iconKey(), request.permissionCode(), request.sortOrder(),
                request.visible(), normalizeStatus(request.menuStatus(), menu.getMenuStatus()), false);
        menu.setUpdatedAt(nowUtc());
        navigationRepository.updateMenu(menu);
        AdminMenuResult after = getMenu(menuId);
        audit(actor, "PLATFORM_MENU", menuId, "UPDATE_MENU", before, after);
        return after;
    }

    private AdminMenuResult toResult(PlatformMenuEntity menu) {
        return new AdminMenuResult(
                stringId(menu.getId()),
                stringId(menu.getParentId()),
                menu.getMenuCode(),
                menu.getMenuName(),
                menu.getMenuType(),
                menu.getRoutePath(),
                menu.getComponentKey(),
                menu.getIconKey(),
                menu.getPermissionCode(),
                menu.getSortOrder(),
                menu.getVisible(),
                menu.getMenuStatus(),
                menu.getRoutePath() == null || KNOWN_ROUTES.contains(menu.getRoutePath()),
                UtcTimeFormatter.toUtcOffsetDateTime(menu.getCreatedAt()),
                UtcTimeFormatter.toUtcOffsetDateTime(menu.getUpdatedAt()));
    }

    private int normalizePageNum(Integer pageNum) {
        return pageNum == null || pageNum < 1 ? DEFAULT_PAGE_NUM : pageNum;
    }

    private int normalizePageSize(Integer pageSize) {
        if (pageSize == null || pageSize < 1) {
            return DEFAULT_PAGE_SIZE;
        }
        return Math.min(pageSize, MAX_PAGE_SIZE);
    }

    private String trimToNull(String value) {
        if (value == null || value.isBlank()) {
            return null;
        }
        return value.trim();
    }

    private Long parseId(String id, String message) {
        try {
            return Long.valueOf(id);
        } catch (NumberFormatException exception) {
            throw new AdminOperationException(HttpStatus.BAD_REQUEST, "ADMIN_INVALID_REQUEST", message);
        }
    }

    private AdminOperationException notFound(String message) {
        return new AdminOperationException(HttpStatus.NOT_FOUND, "ADMIN_TARGET_NOT_FOUND", message);
    }

    private AdminOperationException invalidRequest(String message) {
        return new AdminOperationException(HttpStatus.BAD_REQUEST, "ADMIN_INVALID_REQUEST", message);
    }

    private AdminOperationException conflict(String message) {
        return new AdminOperationException(HttpStatus.CONFLICT, "ADMIN_CONFLICT", message);
    }

    private String stringId(Long id) {
        return id == null ? null : id.toString();
    }

    private void applyMenuFields(
            PlatformMenuEntity menu,
            String parentId,
            String menuName,
            String menuType,
            String routePath,
            String componentKey,
            String iconKey,
            String permissionCode,
            Integer sortOrder,
            Boolean visible,
            String menuStatus,
            boolean creating) {
        menu.setParentId(parseNullableId(parentId, "父菜单 ID 不合法。"));
        if (menu.getParentId() != null && navigationRepository.findMenuById(menu.getParentId()).isEmpty()) {
            throw invalidRequest("父菜单不存在。");
        }
        menu.setMenuName(requireText(menuName, "菜单名称不能为空。"));
        menu.setMenuType(normalizeMenuType(menuType));
        menu.setRoutePath(trimToNull(routePath));
        menu.setComponentKey(trimToNull(componentKey));
        menu.setIconKey(trimToNull(iconKey));
        String normalizedPermissionCode = trimToNull(permissionCode);
        if (normalizedPermissionCode != null
                && accessRepository.findPermissionByCode(normalizedPermissionCode)
                .filter(permission -> PlatformPermissionStatus.ACTIVE.name().equals(permission.getPermissionStatus()))
                .isEmpty()) {
            throw invalidRequest("菜单绑定的权限码不存在或未启用。");
        }
        menu.setPermissionCode(normalizedPermissionCode);
        menu.setSortOrder(sortOrder == null ? 0 : sortOrder);
        menu.setVisible(visible == null ? (creating ? false : Boolean.TRUE.equals(menu.getVisible())) : visible);
        validateMenuStatus(menuStatus);
        menu.setMenuStatus(menuStatus);
    }

    private Long parseNullableId(String id, String message) {
        String normalized = trimToNull(id);
        return normalized == null ? null : parseId(normalized, message);
    }

    private String requireText(String value, String message) {
        String normalized = trimToNull(value);
        if (normalized == null) {
            throw invalidRequest(message);
        }
        return normalized;
    }

    private String requireCode(String value, String message) {
        String normalized = requireText(value, message).toUpperCase(Locale.ROOT);
        if (!normalized.matches("[A-Z0-9_]{3,128}")) {
            throw invalidRequest("菜单代码只能包含大写字母、数字和下划线。");
        }
        return normalized;
    }

    private String normalizeMenuType(String menuType) {
        String normalized = trimToNull(menuType);
        if (normalized == null) {
            return "PAGE";
        }
        normalized = normalized.toUpperCase(Locale.ROOT);
        if (!Set.of("GROUP", "PAGE", "ACTION").contains(normalized)) {
            throw invalidRequest("菜单类型不合法。");
        }
        return normalized;
    }

    private String normalizeStatus(String value, String defaultStatus) {
        String normalized = trimToNull(value);
        return normalized == null ? defaultStatus : normalized.toUpperCase(Locale.ROOT);
    }

    private void validateMenuStatus(String status) {
        if (!PlatformMenuStatus.ACTIVE.name().equals(status) && !PlatformMenuStatus.DISABLED.name().equals(status)) {
            throw invalidRequest("菜单状态不合法。");
        }
    }

    private LocalDateTime nowUtc() {
        return LocalDateTime.now(ZoneOffset.UTC);
    }

    private void audit(
            AuthenticatedUserContext actor,
            String targetType,
            String targetId,
            String action,
            Object before,
            Object after) {
        auditLogService.record(new PlatformAdminAuditLogDraft(
                actor,
                targetType,
                targetId,
                action,
                toJson(before),
                toJson(after)));
    }

    private String toJson(Object value) {
        if (value == null) {
            return null;
        }
        try {
            return objectMapper.writeValueAsString(value);
        } catch (JsonProcessingException exception) {
            throw new AdminOperationException(
                    HttpStatus.INTERNAL_SERVER_ERROR,
                    "ADMIN_AUDIT_SERIALIZE_FAILED",
                    "系统管理审计序列化失败。");
        }
    }
}
