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

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.hotel.common.enums.PlatformHotelStatus;
import cn.nianxx.thhotel.platform.hotel.common.request.AdminHotelCreateRequest;
import cn.nianxx.thhotel.platform.hotel.common.request.AdminHotelStatusUpdateRequest;
import cn.nianxx.thhotel.platform.hotel.common.request.AdminHotelUpdateRequest;
import cn.nianxx.thhotel.platform.hotel.common.result.AdminHotelResult;
import cn.nianxx.thhotel.platform.hotel.domain.PlatformHotelEntity;
import cn.nianxx.thhotel.platform.hotel.repository.PlatformHotelRepository;
import cn.nianxx.thhotel.platform.hotel.service.AdminHotelManagementService;
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.ZoneId;
import java.time.ZoneOffset;
import java.util.Locale;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 管理后台酒店管理服务实现。单酒店阶段必须主动保护唯一 ACTIVE 酒店约束。
 */
@Service
public class AdminHotelManagementServiceImpl implements AdminHotelManagementService {

    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 final PlatformHotelRepository hotelRepository;
    private final PlatformAdminAuditLogService auditLogService;
    private final ObjectMapper objectMapper;

    /**
     * 注入酒店仓储。
     */
    public AdminHotelManagementServiceImpl(
            PlatformHotelRepository hotelRepository,
            PlatformAdminAuditLogService auditLogService,
            ObjectMapper objectMapper) {
        this.hotelRepository = hotelRepository;
        this.auditLogService = auditLogService;
        this.objectMapper = objectMapper;
    }

    /**
     * 分页查询酒店列表。
     */
    @Override
    public PlatformPageResult<AdminHotelResult> queryHotels(
            String keyword,
            String hotelStatus,
            Integer pageNum,
            Integer pageSize) {
        int normalizedPageNum = normalizePageNum(pageNum);
        int normalizedPageSize = normalizePageSize(pageSize);
        PlatformPageSnapshot<PlatformHotelEntity> page = hotelRepository.queryHotels(
                trimToNull(keyword),
                trimToNull(hotelStatus),
                normalizedPageNum,
                normalizedPageSize);
        return new PlatformPageResult<>(
                page.items().stream().map(this::toResult).toList(),
                new PlatformPaginationResult(page.pageNum(), page.pageSize(), page.total()));
    }

    /**
     * 按酒店业务 ID 查询酒店详情。
     */
    @Override
    public AdminHotelResult getHotel(String hotelId) {
        return hotelRepository.findHotelByHotelId(hotelId)
                .map(this::toResult)
                .orElseThrow(() -> notFound("酒店不存在。"));
    }

    /**
     * 新增酒店。单酒店阶段新增后默认 DISABLED，避免触发唯一 ACTIVE 酒店约束。
     */
    @Override
    @Transactional
    public AdminHotelResult createHotel(AdminHotelCreateRequest request, AuthenticatedUserContext actor) {
        if (request == null) {
            throw invalidRequest("酒店创建请求不能为空。");
        }
        String hotelId = requireHotelId(request.hotelId());
        if (hotelRepository.findHotelByHotelId(hotelId).isPresent()) {
            throw conflict("酒店 ID 已存在。");
        }
        PlatformHotelEntity hotel = new PlatformHotelEntity();
        hotel.setHotelId(hotelId);
        hotel.setHotelName(requireText(request.hotelName(), "酒店名称不能为空。"));
        hotel.setTimeZone(requireTimeZone(request.timeZone()));
        hotel.setHotelStatus(PlatformHotelStatus.DISABLED.name());
        hotel.setSortOrder(request.sortOrder() == null ? 0 : request.sortOrder());
        hotel.setCreatedAt(nowUtc());
        hotel.setUpdatedAt(nowUtc());
        hotelRepository.insertHotel(hotel);
        AdminHotelResult after = getHotel(hotel.getHotelId());
        audit(actor, "PLATFORM_HOTEL", hotel.getHotelId(), "CREATE_HOTEL", null, after);
        return after;
    }

    /**
     * 编辑酒店基础信息。hotelId 是稳定业务键，创建后不允许修改。
     */
    @Override
    @Transactional
    public AdminHotelResult updateHotel(
            String hotelId,
            AdminHotelUpdateRequest request,
            AuthenticatedUserContext actor) {
        if (request == null) {
            throw invalidRequest("酒店更新请求不能为空。");
        }
        PlatformHotelEntity hotel = findHotelOrThrow(hotelId);
        AdminHotelResult before = toResult(hotel);
        hotel.setHotelName(requireText(request.hotelName(), "酒店名称不能为空。"));
        hotel.setTimeZone(requireTimeZone(request.timeZone()));
        hotel.setSortOrder(request.sortOrder() == null ? hotel.getSortOrder() : request.sortOrder());
        hotel.setUpdatedAt(nowUtc());
        hotelRepository.updateHotel(hotel);
        AdminHotelResult after = getHotel(hotel.getHotelId());
        audit(actor, "PLATFORM_HOTEL", hotel.getHotelId(), "UPDATE_HOTEL", before, after);
        return after;
    }

    /**
     * 更新酒店状态。启用时拒绝第二家 ACTIVE，禁用时拒绝最后一家 ACTIVE。
     */
    @Override
    @Transactional
    public AdminHotelResult updateHotelStatus(
            String hotelId,
            AdminHotelStatusUpdateRequest request,
            AuthenticatedUserContext actor) {
        if (request == null) {
            throw invalidRequest("酒店状态更新请求不能为空。");
        }
        PlatformHotelEntity hotel = findHotelOrThrow(hotelId);
        AdminHotelResult before = toResult(hotel);
        String status = normalizeStatus(request.hotelStatus(), hotel.getHotelStatus());
        validateHotelStatus(status);
        if (PlatformHotelStatus.ACTIVE.name().equals(status)
                && hotelRepository.countActiveHotelsExcluding(hotel.getHotelId()) > 0) {
            throw conflict("单酒店阶段不允许启用第二家 ACTIVE 酒店。");
        }
        if (PlatformHotelStatus.DISABLED.name().equals(status)
                && PlatformHotelStatus.ACTIVE.name().equals(hotel.getHotelStatus())
                && hotelRepository.countActiveHotels() <= 1) {
            throw conflict("不能禁用最后一家 ACTIVE 酒店。");
        }
        hotel.setHotelStatus(status);
        hotel.setUpdatedAt(nowUtc());
        hotelRepository.updateHotel(hotel);
        AdminHotelResult after = getHotel(hotel.getHotelId());
        audit(actor, "PLATFORM_HOTEL", hotel.getHotelId(), "UPDATE_HOTEL_STATUS", before, after);
        return after;
    }

    private AdminHotelResult toResult(PlatformHotelEntity hotel) {
        return new AdminHotelResult(
                stringId(hotel.getId()),
                hotel.getHotelId(),
                hotel.getHotelName(),
                hotel.getHotelStatus(),
                hotel.getTimeZone(),
                hotel.getSortOrder(),
                hotelRepository.countAuthorizedUsersByHotelId(hotel.getHotelId()),
                UtcTimeFormatter.toUtcOffsetDateTime(hotel.getCreatedAt()),
                UtcTimeFormatter.toUtcOffsetDateTime(hotel.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 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 PlatformHotelEntity findHotelOrThrow(String hotelId) {
        String normalizedHotelId = trimToNull(hotelId);
        if (normalizedHotelId == null) {
            throw invalidRequest("酒店 ID 不能为空。");
        }
        return hotelRepository.findHotelByHotelId(normalizedHotelId)
                .orElseThrow(() -> notFound("酒店不存在。"));
    }

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

    private String requireHotelId(String value) {
        String normalized = requireText(value, "酒店 ID 不能为空。").toUpperCase(Locale.ROOT);
        if (!normalized.matches("[A-Z0-9_-]{3,64}")) {
            throw invalidRequest("酒店 ID 只能包含大写字母、数字、下划线和中划线。");
        }
        return normalized;
    }

    private String requireTimeZone(String value) {
        String normalized = requireText(value, "酒店时区不能为空。");
        try {
            ZoneId.of(normalized);
            return normalized;
        } catch (RuntimeException exception) {
            throw invalidRequest("酒店时区不合法。");
        }
    }

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

    private void validateHotelStatus(String status) {
        if (!PlatformHotelStatus.ACTIVE.name().equals(status) && !PlatformHotelStatus.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",
                    "系统管理审计序列化失败。");
        }
    }
}
