package io.github.shirainbown.sharelink.link;

import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebInputException;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.endpoint.CustomEndpoint;
import run.halo.app.extension.GroupVersion;
import run.halo.app.extension.ListOptions;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.extension.index.query.QueryFactory;
import io.github.shirainbown.sharelink.common.OneTimeTokenManager;
import io.github.shirainbown.sharelink.model.ProtectedLink;
import io.github.shirainbown.sharelink.verify.VerifiedTokenIssuer;

/**
 * Public endpoint exchanging a (possibly verified) identity for a one-time protected
 * link token. Mounted under {@code /apis/api.sharelink.halo.run/v1alpha1} and opened to
 * anonymous visitors via the aggregate-to-anonymous role template.
 */
@Component
@RequiredArgsConstructor
public class LinkTokenEndpoint implements CustomEndpoint {

    private final ReactiveExtensionClient client;
    private final VerifiedTokenIssuer verifiedTokenIssuer;

    @Override
    public RouterFunction<ServerResponse> endpoint() {
        return RouterFunctions.route()
            .POST("links/-/token", this::issueToken)
            .build();
    }

    private Mono<ServerResponse> issueToken(ServerRequest request) {
        return request.bodyToMono(TokenRequest.class)
            .switchIfEmpty(Mono.error(new ServerWebInputException("请求体不能为空")))
            .flatMap(body -> {
                var slug = StringUtils.trimToNull(body.slug());
                if (slug == null) {
                    return Mono.error(new ServerWebInputException("slug 不能为空"));
                }
                return findBySlug(slug)
                    .switchIfEmpty(Mono.error(new ResponseStatusException(
                        HttpStatus.NOT_FOUND, "链接不存在")))
                    .flatMap(link -> doIssueToken(request, link, body));
            });
    }

    private Mono<ServerResponse> doIssueToken(ServerRequest request, ProtectedLink link,
                                              TokenRequest body) {
        var spec = link.getSpec();
        if (!spec.isEnabled()) {
            return Mono.error(new ResponseStatusException(HttpStatus.FORBIDDEN, "该链接已停用"));
        }
        var slug = spec.getSlug();
        return verifiedTokenIssuer.issue(OneTimeTokenManager.SCOPE_LINK, slug,
                spec.isRequireEmailVerify(), body.email(), body.code(),
                request.exchange().getRequest())
            .flatMap(token -> ServerResponse.ok().bodyValue(new TokenResponse(token,
                "/link/" + slug + "/go?token=" + token)));
    }

    private Mono<ProtectedLink> findBySlug(String slug) {
        var options = ListOptions.builder()
            .fieldQuery(QueryFactory.equal("spec.slug", slug))
            .build();
        return client.listAll(ProtectedLink.class, options, Sort.unsorted()).next();
    }

    @Override
    public GroupVersion groupVersion() {
        return GroupVersion.parseAPIVersion("api.sharelink.halo.run/v1alpha1");
    }

    public record TokenRequest(String slug, String email, String code) {
    }

    public record TokenResponse(String token, String url) {
    }
}
