package ink.wgink.gateway.handler.route;

import ink.wgink.gateway.consts.ISystemConst;
import ink.wgink.gateway.dao.route.IRouteDao;
import ink.wgink.gateway.exception.ParamsException;
import ink.wgink.gateway.exception.SearchException;
import ink.wgink.gateway.handler.BaseHandler;
import ink.wgink.gateway.pojo.result.SuccessResult;
import ink.wgink.gateway.pojo.route.Route;
import ink.wgink.gateway.util.RegexUtil;
import ink.wgink.gateway.util.RequestFieldCheckUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

/**
 * When you feel like quitting. Think about why you started
 * 当你想要放弃的时候，想想当初你为何开始
 *
 * @ClassName: RouteHandler
 * @Description: 路由
 * @Author: WangGeng
 * @Date: 2021/4/19 21:40
 * @Version: 1.0
 **/
@Service
public class RouteHandler extends BaseHandler implements ApplicationEventPublisherAware {

    private IRouteDao routeDao;
    private ApplicationEventPublisher applicationEventPublisher;

    public RouteHandler(IRouteDao routeDao) {
        this.routeDao = routeDao;
    }

    /**
     * 新增
     *
     * @param serverRequest
     * @return
     */
    public Mono<ServerResponse> save(ServerRequest serverRequest) {
        Mono<Route> routeMono = serverRequest.bodyToMono(Route.class);
        return routeMono.flatMap(route -> {
            setSave(route);

            RequestFieldCheckUtil.check(route);
            if (route.getPath().startsWith(ISystemConst.ROOT_PATH_PREFIX)) {
                return Mono.error(new ParamsException("根路径不能配置通配符"));
            }
            if (route.getPath().startsWith(ISystemConst.ADMIN_ROUTER_PREFIX)) {
                return Mono.error(new ParamsException("根路径不能以/wg/*开头"));
            }
            if (!StringUtils.isBlank(route.getSetPath()) && !RegexUtil.isPath(route.getSetPath())) {
                return Mono.error(new ParamsException("设置路径格式错误"));
            }
            // 判断路径是否存在
            Route routeExample = new Route();
            routeExample.setPath(route.getPath());
            ExampleMatcher exampleMatcher = ExampleMatcher.matching().withMatcher("path", ExampleMatcher.GenericPropertyMatcher::exact).withIgnoreCase("id");
            Example example = Example.of(routeExample, exampleMatcher);
            return routeDao.findOne(example).flatMap(
                    r -> Mono.error(new SearchException("路径已经存在"))
            ).switchIfEmpty(routeDao.save(route));
        }).then(ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).body(Flux.just(new SuccessResult()), SuccessResult.class));
    }

    /**
     * 删除
     *
     * @param serverRequest
     * @return
     */
    public Mono<ServerResponse> delete(ServerRequest serverRequest) {
        String ids = serverRequest.pathVariable("ids");
        List<String> idList = Arrays.asList(ids.split(","));
        return routeDao.findAllById(idList).flatMap(
                route -> routeDao.delete(route)
        ).then(ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).body(Mono.just(new SuccessResult()), SuccessResult.class));
    }

    /**
     * 修改
     *
     * @param serverRequest
     * @return
     */
    public Mono<ServerResponse> update(ServerRequest serverRequest) {
        String id = serverRequest.pathVariable("id");
        Mono<Route> routeMono = serverRequest.bodyToMono(Route.class);
        return routeDao.findById(id).flatMap(route -> {
            setUpdate(route);
            return routeMono.flatMap(r -> {
                route.setTitle(r.getTitle());
                route.setSummary(r.getSummary());
                route.setPath(r.getPath());
                route.setUri(r.getUri());
                route.setTestPath(r.getTestPath());
                route.setRouteType(r.getRouteType());
                route.setSetPath(r.getSetPath());

                RequestFieldCheckUtil.check(r);
                if (r.getPath().startsWith(ISystemConst.ROOT_PATH_PREFIX)) {
                    return Mono.error(new ParamsException("根路径不能配置通配符"));
                }
                if (r.getPath().startsWith(ISystemConst.ADMIN_ROUTER_PREFIX)) {
                    return Mono.error(new ParamsException("根路径不能以/wg/*开头"));
                }
                if (!StringUtils.isBlank(route.getSetPath()) && !RegexUtil.isPath(route.getSetPath())) {
                    return Mono.error(new ParamsException("设置路径格式错误"));
                }

                // 检索路径是否存在
                Route routeExample = new Route();
                routeExample.setPath(r.getPath());
                ExampleMatcher exampleMatcher = ExampleMatcher.matching()
                        .withMatcher("path", ExampleMatcher.GenericPropertyMatcher::exact)
                        .withIgnoreCase("id");
                Example example = Example.of(routeExample, exampleMatcher);
                return routeDao.findOne(example).flatMap(er -> {
                    Route existRoute = (Route) er;
                    // 如果已经存在
                    if (!StringUtils.equals(existRoute.getUuid(), id)) {
                        return Mono.error(new SearchException("路径已经存在"));
                    }
                    return routeDao.save(route);
                }).switchIfEmpty(routeDao.save(route));
            });
        }).then(ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).body(Flux.just(new SuccessResult()), SuccessResult.class));
    }

    /**
     * 列表
     *
     * @param serverRequest
     * @return
     */
    public Mono<ServerResponse> list(ServerRequest serverRequest) {
        Optional<String> keywords = serverRequest.queryParam("keywords");
        Optional<String> routeType = serverRequest.queryParam("routeType");

        Route route = new Route();
        Example example = Example.of(route);
        if (keywords.isPresent() || routeType.isPresent()) {
            if (keywords.isPresent()) {
                String keywordTrim = keywords.get().trim();
                route.setTitle(keywordTrim);
                route.setSummary(keywordTrim);
            }
            if (routeType.isPresent()) {
                String routeTypeTrim = routeType.get().trim();
                route.setRouteType(routeTypeTrim);
            }
            ExampleMatcher exampleMatcher = ExampleMatcher.matchingAny()
                    .withMatcher("title", ExampleMatcher.GenericPropertyMatcher::contains)
                    .withMatcher("summary", ExampleMatcher.GenericPropertyMatcher::contains)
                    .withMatcher("routeType", ExampleMatcher.GenericPropertyMatcher::exact)
                    .withIgnoreCase("id");
            example = Example.of(route, exampleMatcher);
        }
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).body(routeDao.findAll(example), Route.class);
    }

    /**
     * 列表
     *
     * @param serverRequest
     * @return
     */
    public Mono<ServerResponse> get(ServerRequest serverRequest) {
        String id = serverRequest.pathVariable("id");
        return routeDao.findById(id).flatMap(
                route -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).body(Mono.just(route), Route.class)
        ).switchIfEmpty(ServerResponse.notFound().build());
    }

    /**
     * 通知刷新路由
     *
     * @param serverRequest
     * @return
     */
    public Mono<ServerResponse> refresh(ServerRequest serverRequest) {
        this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(Mono.just(new SuccessResult()), SuccessResult.class);
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }
}
