package cn.novalon.gym.manage.gateway.service.impl;

import cn.novalon.gym.manage.gateway.service.IDynamicRouteService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 动态路由服务实现类
 * 
 * 文件定义：实现网关路由的动态配置和管理
 * 涉及业务：路由增删改查、路由刷新、路由缓存管理
 * 
 * 核心功能：
 * 1. 动态添加路由
 * 2. 动态删除路由
 * 3. 动态更新路由
 * 4. 路由列表查询
 * 5. 路由刷新
 * 
 * @author 张翔
 * @date 2026-04-14
 */
@Service
public class DynamicRouteService implements IDynamicRouteService {

    private static final Logger logger = LoggerFactory.getLogger(DynamicRouteService.class);

    private final RouteDefinitionWriter routeDefinitionWriter;
    private final RouteDefinitionLocator routeDefinitionLocator;
    private final ApplicationEventPublisher publisher;

    private final Map<String, RouteDefinition> routeCache = new ConcurrentHashMap<>();

    public DynamicRouteService(
            RouteDefinitionWriter routeDefinitionWriter,
            RouteDefinitionLocator routeDefinitionLocator,
            ApplicationEventPublisher publisher) {
        this.routeDefinitionWriter = routeDefinitionWriter;
        this.routeDefinitionLocator = routeDefinitionLocator;
        this.publisher = publisher;

        initializeRouteCache();
    }

    private void initializeRouteCache() {
        routeDefinitionLocator.getRouteDefinitions()
                .doOnNext(route -> routeCache.put(route.getId(), route))
                .subscribe(
                        route -> logger.debug("Cached route: {}", route.getId()),
                        error -> logger.error("Failed to initialize route cache", error),
                        () -> logger.info("Route cache initialized with {} routes", routeCache.size())
                );
    }

    @Override
    public Mono<Boolean> addRoute(RouteDefinition routeDefinition) {
        if (routeDefinition == null || routeDefinition.getId() == null) {
            logger.error("Invalid route definition: route or route ID is null");
            return Mono.just(false);
        }

        String routeId = routeDefinition.getId();
        logger.info("Adding route: {}", routeId);

        return routeDefinitionWriter.save(Mono.just(routeDefinition))
                .then(Mono.fromRunnable(() -> routeCache.put(routeId, routeDefinition)))
                .then(refreshRoutes())
                .then(Mono.fromRunnable(() -> logger.info("Route added successfully: {}", routeId)))
                .thenReturn(true)
                .onErrorResume(error -> {
                    logger.error("Failed to add route: {}", routeId, error);
                    return Mono.just(false);
                });
    }

    @Override
    public Mono<Boolean> updateRoute(RouteDefinition routeDefinition) {
        if (routeDefinition == null || routeDefinition.getId() == null) {
            logger.error("Invalid route definition: route or route ID is null");
            return Mono.just(false);
        }

        String routeId = routeDefinition.getId();
        
        if (!routeCache.containsKey(routeId)) {
            logger.warn("Route not found for update: {}", routeId);
            return Mono.just(false);
        }

        logger.info("Updating route: {}", routeId);

        return routeDefinitionWriter.delete(Mono.just(routeId))
                .then(routeDefinitionWriter.save(Mono.just(routeDefinition)))
                .then(Mono.fromRunnable(() -> routeCache.put(routeId, routeDefinition)))
                .then(refreshRoutes())
                .then(Mono.fromRunnable(() -> logger.info("Route updated successfully: {}", routeId)))
                .thenReturn(true)
                .onErrorResume(error -> {
                    logger.error("Failed to update route: {}", routeId, error);
                    return Mono.just(false);
                });
    }

    @Override
    public Mono<Boolean> deleteRoute(String routeId) {
        if (routeId == null) {
            logger.error("Invalid route ID: null");
            return Mono.just(false);
        }

        if (!routeCache.containsKey(routeId)) {
            logger.warn("Route not found for deletion: {}", routeId);
            return Mono.just(false);
        }

        logger.info("Deleting route: {}", routeId);

        return routeDefinitionWriter.delete(Mono.just(routeId))
                .then(Mono.fromRunnable(() -> routeCache.remove(routeId)))
                .then(refreshRoutes())
                .then(Mono.fromRunnable(() -> logger.info("Route deleted successfully: {}", routeId)))
                .thenReturn(true)
                .onErrorResume(error -> {
                    logger.error("Failed to delete route: {}", routeId, error);
                    return Mono.just(false);
                });
    }

    @Override
    public Flux<RouteDefinition> getRoutes() {
        return Flux.fromIterable(routeCache.values());
    }

    @Override
    public Mono<RouteDefinition> getRoute(String routeId) {
        if (routeId == null) {
            return Mono.empty();
        }
        return Mono.justOrEmpty(routeCache.get(routeId));
    }

    @Override
    public Mono<Void> refreshRoutes() {
        return Mono.fromRunnable(() -> {
            publisher.publishEvent(new RefreshRoutesEvent(this));
            logger.info("Routes refreshed");
        });
    }

    @Override
    public Mono<Long> getRouteCount() {
        return Mono.just((long) routeCache.size());
    }

    @Override
    public Mono<Boolean> routeExists(String routeId) {
        if (routeId == null) {
            return Mono.just(false);
        }
        return Mono.just(routeCache.containsKey(routeId));
    }

    @Override
    public Mono<Void> clearRouteCache() {
        return Mono.fromRunnable(() -> {
            routeCache.clear();
            logger.info("Route cache cleared");
        });
    }
}