package io.github.shirainbown.sharelink.verify;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebInputException;
import reactor.core.publisher.Mono;
import io.github.shirainbown.sharelink.SharelinkSettingConfigGetter;
import io.github.shirainbown.sharelink.common.OneTimeTokenManager;
import io.github.shirainbown.sharelink.emailcode.EmailCodeManager;
import io.github.shirainbown.sharelink.util.EmailUtils;
import io.github.shirainbown.sharelink.util.IpUtils;

/**
 * Shared issuance logic for the download and protected-link flows: a visitor either
 * passes a previously verified email, or proves it with a fresh verification code, and
 * receives a one-time token bound to the given resource scope.
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class VerifiedTokenIssuer {

    private final SharelinkSettingConfigGetter settingConfigGetter;
    private final VerifiedEmailService verifiedEmailService;
    private final EmailCodeManager emailCodeManager;
    private final OneTimeTokenManager tokenManager;

    /**
     * Issues a one-time token for {@code scope}/{@code slug}.
     *
     * @param requireVerify when {@code false} the token is issued immediately; otherwise
     * the visitor must either be a previously verified email or present a valid code
     * @return the issued token
     */
    public Mono<String> issue(String scope, String slug, boolean requireVerify,
                              String email, String code, ServerHttpRequest request) {
        var clientIp = IpUtils.clientIp(request);
        if (!requireVerify) {
            return Mono.just(tokenManager.issue(scope, slug, null, clientIp));
        }
        var normalizedEmail = EmailUtils.normalizeEmail(email);
        if (normalizedEmail == null || !EmailUtils.isValidEmail(normalizedEmail)) {
            return Mono.error(new ServerWebInputException("邮箱格式不正确"));
        }
        return verifiedEmailService.isVerified(normalizedEmail)
            .onErrorResume(e -> {
                log.warn("查询邮箱验证状态失败，按未验证处理: {}", normalizedEmail, e);
                return Mono.just(false);
            })
            .flatMap(verified -> {
                if (verified) {
                    return Mono.just(
                        tokenManager.issue(scope, slug, normalizedEmail, clientIp));
                }
                var codeValue = StringUtils.trimToNull(code);
                if (codeValue == null) {
                    return Mono.error(new ResponseStatusException(
                        HttpStatus.FORBIDDEN, "请先完成邮箱验证"));
                }
                return settingConfigGetter.getEmailVerifyConfig()
                    .flatMap(config -> emailCodeManager.verify(normalizedEmail, codeValue,
                            config)
                        .flatMap(valid -> {
                            if (!valid) {
                                return Mono.error(new ResponseStatusException(
                                    HttpStatus.FORBIDDEN, "验证码错误或已过期，请重新获取"));
                            }
                            return verifiedEmailService.recordVerified(normalizedEmail,
                                    clientIp)
                                .onErrorResume(e -> {
                                    log.warn("登记已验证邮箱失败: {}", normalizedEmail, e);
                                    return Mono.empty();
                                })
                                .thenReturn(tokenManager.issue(scope, slug, normalizedEmail,
                                    clientIp));
                        }));
            });
    }
}
