package cn.nianxx.thhotel.platform.message.control;

import cn.nianxx.thhotel.platform.access.common.enums.PlatformPermissionCode;
import cn.nianxx.thhotel.platform.hotel.service.HotelContextService;
import cn.nianxx.thhotel.platform.message.common.result.SourceMessagePageResult;
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageConversationResult;
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageOriginalResponse;
import cn.nianxx.thhotel.platform.message.common.request.SourceMessageOriginalAccessRequest;
import cn.nianxx.thhotel.platform.message.common.request.SourceMessageQueryRequest;
import cn.nianxx.thhotel.platform.security.service.FrontendAuthorizationService;
import cn.nianxx.thhotel.platform.message.service.SourceMessageConversationService;
import cn.nianxx.thhotel.platform.message.service.SourceMessageOriginalService;
import cn.nianxx.thhotel.platform.message.service.SourceMessageQueryService;
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageSummaryResponse;
import cn.nianxx.thhotel.platform.security.common.dto.AuthenticatedUserContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

/**
 * SourceMessage Inbox 查询接口。摘要接口只返回安全信息，原文和会话正文接口必须额外校验原文读取权限。
 */
@RestController
@RequestMapping("/api/source-messages")
public class SourceMessageController {

    private static final String FRONTEND_ACTOR_PREFIX = "frontend_user:";
    private static final String ORIGINAL_ACCESS_SCENE = "source-message-original";
    private static final String CONVERSATION_ACCESS_SCENE = "source-message-conversation";

    private final SourceMessageQueryService queryService;
    private final SourceMessageOriginalService originalService;
    private final SourceMessageConversationService conversationService;
    private final FrontendAuthorizationService authorizationService;
    private final HotelContextService hotelContextService;

    /**
     * 注入 SourceMessage 查询与原文读取服务，Controller 不直接访问 Mapper 或 Repository。
     */
    public SourceMessageController(
            SourceMessageQueryService queryService,
            SourceMessageOriginalService originalService,
            SourceMessageConversationService conversationService,
            FrontendAuthorizationService authorizationService,
            HotelContextService hotelContextService) {
        this.queryService = queryService;
        this.originalService = originalService;
        this.conversationService = conversationService;
        this.authorizationService = authorizationService;
        this.hotelContextService = hotelContextService;
    }

    /**
     * 分页查询 SourceMessage Inbox 安全摘要，普通列表不返回正文、HTML、附件 URL 或原始 payload。
     */
    @GetMapping
    public SourceMessagePageResult<SourceMessageSummaryResponse> list(
            @RequestParam(name = "hotel_id", required = false) String hotelId,
            @RequestParam(name = "hotelId", required = false) String legacyHotelId,
            @RequestParam(name = "external_message_id", required = false) String externalMessageId,
            @RequestParam(name = "externalMessageId", required = false) String legacyExternalMessageId,
            @RequestParam(name = "external_conversation_id", required = false) String externalConversationId,
            @RequestParam(name = "externalConversationId", required = false) String legacyExternalConversationId,
            @RequestParam(required = false) String captureStatus,
            @RequestParam(name = "page_num", required = false) Integer pageNum,
            @RequestParam(name = "pageNum", required = false) Integer legacyPageNum,
            @RequestParam(name = "page_size", required = false) Integer pageSize,
            @RequestParam(name = "pageSize", required = false) Integer legacyPageSize) {
        authorizationService.requirePermission(PlatformPermissionCode.SOURCE_MESSAGE_READ.name());
        return queryService.query(new SourceMessageQueryRequest(
                firstText(hotelId, legacyHotelId),
                firstText(externalMessageId, legacyExternalMessageId),
                firstText(externalConversationId, legacyExternalConversationId),
                captureStatus,
                firstInteger(pageNum, legacyPageNum),
                firstInteger(pageSize, legacyPageSize)
        ));
    }

    /**
     * 读取单条 SourceMessage Inbox 安全详情，用于排查消息是否已入站。
     */
    @GetMapping("/{id}")
    public SourceMessageSummaryResponse detail(@PathVariable Long id) {
        authorizationService.requirePermission(PlatformPermissionCode.SOURCE_MESSAGE_READ.name());
        SourceMessageSummaryResponse summary = queryService.getSummary(id)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "source message not found"));
        requireSourceMessageHotelAccess(summary);
        return summary;
    }

    /**
     * 读取 SourceMessage 原文内容。必须登录且拥有原文读取权限，并按消息所属酒店校验访问权。
     */
    @GetMapping("/{id}/original")
    public SourceMessageOriginalResponse original(@PathVariable Long id) {
        AuthenticatedUserContext actor = requireSourceMessageOriginalReadPermission();
        SourceMessageSummaryResponse summary = requireExistingSourceMessage(id);
        requireSourceMessageHotelAccess(summary);
        return originalService.readOriginal(
                        id,
                        new SourceMessageOriginalAccessRequest(actorId(actor), ORIGINAL_ACCESS_SCENE))
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "source message not found"));
    }

    /**
     * 读取指定 SourceMessage 所在邮件会话完整详情。必须拥有原文读取权限，前端不传原文 key。
     */
    @GetMapping("/{id}/conversation")
    public SourceMessageConversationResult conversation(@PathVariable Long id) {
        AuthenticatedUserContext actor = requireSourceMessageOriginalReadPermission();
        SourceMessageSummaryResponse summary = requireExistingSourceMessage(id);
        requireSourceMessageHotelAccess(summary);
        return conversationService.getConversation(
                        id,
                        new SourceMessageOriginalAccessRequest(actorId(actor), CONVERSATION_ACCESS_SCENE))
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "source message not found"));
    }

    /**
     * 校验查询参数文本是否有效，避免空白参数影响兼容参数选择。
     */
    private boolean hasText(String value) {
        return value != null && !value.trim().isEmpty();
    }

    /**
     * 优先使用当前规范 snake_case 参数，兼容早期 camelCase 查询参数。
     */
    private String firstText(String first, String second) {
        return hasText(first) ? first : second;
    }

    /**
     * 优先使用当前规范 snake_case 分页参数，兼容早期 camelCase 查询参数。
     */
    private Integer firstInteger(Integer first, Integer second) {
        return first == null ? second : first;
    }

    /**
     * 按 SourceMessage 实际归属酒店校验当前用户访问权，避免跨酒店按 ID 读取摘要。
     */
    private void requireSourceMessageHotelAccess(SourceMessageSummaryResponse summary) {
        hotelContextService.requireAccessibleHotel(summary.hotelId());
    }

    /**
     * 查询 SourceMessage 安全摘要用于存在性和酒店访问校验，避免原文读取先于权限边界发生。
     */
    private SourceMessageSummaryResponse requireExistingSourceMessage(Long id) {
        return queryService.getSummary(id)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "source message not found"));
    }

    /**
     * 校验邮件原文读取权限。原文权限必须叠加摘要读取权限，避免自定义角色只拥有原文权限时绕过摘要边界。
     */
    private AuthenticatedUserContext requireSourceMessageOriginalReadPermission() {
        authorizationService.requirePermission(PlatformPermissionCode.SOURCE_MESSAGE_READ.name());
        return authorizationService.requirePermission(PlatformPermissionCode.SOURCE_MESSAGE_ORIGINAL_READ.name());
    }

    /**
     * 生成原文读取审计 actor，使用稳定用户 ID 并加前端用户前缀，便于区分机器和系统内部访问。
     */
    private String actorId(AuthenticatedUserContext actor) {
        return FRONTEND_ACTOR_PREFIX + actor.userId();
    }
}
