package io.github.shirainbown.sharelink.verify;

import java.time.Instant;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import run.halo.app.extension.GroupVersionKind;
import run.halo.app.extension.Metadata;
import run.halo.app.extension.ReactiveExtensionClient;
import io.github.shirainbown.sharelink.SharelinkSettingConfigGetter;
import io.github.shirainbown.sharelink.model.VerifiedDownloader;
import io.github.shirainbown.sharelink.util.EmailUtils;

/**
 * Unified "is this email verified" check for the download flow. Looks up this plugin's
 * own {@link VerifiedDownloader} registry first; when {@code trustCommentVerified} is
 * enabled it also honors the comment plugin's {@code VerifiedCommenter} registry
 * (resolved at runtime by GVK + SHA-256 name, degrading gracefully when the comment
 * plugin is absent).
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class VerifiedEmailService {

    private static final GroupVersionKind COMMENT_VERIFIED_COMMENTER_GVK =
        GroupVersionKind.fromAPIVersionAndKind("commentwidget.halo.run/v1alpha1",
            "VerifiedCommenter");

    private final ReactiveExtensionClient client;
    private final SharelinkSettingConfigGetter settingConfigGetter;

    public Mono<Boolean> isVerified(String email) {
        var normalizedEmail = EmailUtils.normalizeEmail(email);
        if (normalizedEmail == null) {
            return Mono.just(false);
        }
        // Look up by the deterministic metadata name (SHA-256 of the normalized email).
        return client.fetch(VerifiedDownloader.class, EmailUtils.sha256Hex(normalizedEmail))
            .map(downloader -> downloader.getSpec() != null
                && normalizedEmail.equals(downloader.getSpec().getEmail()))
            .defaultIfEmpty(false)
            .flatMap(verified -> verified ? Mono.just(true)
                : isCommentVerified(normalizedEmail));
    }

    /**
     * Checks the comment plugin's verified registry. Any failure (plugin absent, scheme
     * unregistered, store error) degrades to "not verified" without affecting the main
     * flow.
     */
    private Mono<Boolean> isCommentVerified(String normalizedEmail) {
        return settingConfigGetter.getEmailVerifyConfig()
            .flatMap(config -> {
                if (!config.isTrustCommentVerified()) {
                    return Mono.just(false);
                }
                return client.fetch(COMMENT_VERIFIED_COMMENTER_GVK,
                        EmailUtils.sha256Hex(normalizedEmail))
                    .map(unstructured -> true)
                    .defaultIfEmpty(false)
                    .onErrorResume(e -> {
                        log.debug("查询评论插件已验证邮箱失败，按未验证处理: {}", normalizedEmail, e);
                        return Mono.just(false);
                    });
            });
    }

    /**
     * Registers (or refreshes) the email in this plugin's own verified registry.
     */
    public Mono<Void> recordVerified(String email, String ip) {
        var normalizedEmail = EmailUtils.normalizeEmail(email);
        if (normalizedEmail == null) {
            return Mono.empty();
        }
        var name = EmailUtils.sha256Hex(normalizedEmail);
        var now = Instant.now();
        // Retried once to tolerate a concurrent registration of the same email.
        return Mono.defer(() -> upsertVerified(name, normalizedEmail, now))
            .retry(1)
            .then();
    }

    private Mono<VerifiedDownloader> upsertVerified(String name, String email, Instant now) {
        return client.fetch(VerifiedDownloader.class, name)
            .flatMap(existing -> {
                existing.getSpec().setLastVerifiedAt(now);
                return client.update(existing);
            })
            .switchIfEmpty(Mono.defer(() -> {
                var downloader = new VerifiedDownloader();
                var metadata = new Metadata();
                metadata.setName(name);
                downloader.setMetadata(metadata);
                var spec = new VerifiedDownloader.Spec();
                spec.setEmail(email);
                spec.setFirstVerifiedAt(now);
                spec.setLastVerifiedAt(now);
                downloader.setSpec(spec);
                return client.create(downloader);
            }));
    }
}
