package com.ttstd.signaling.service;

import com.ttstd.signaling.model.DeviceBinding;
import com.ttstd.signaling.model.PairingCode;
import com.ttstd.signaling.security.AuthException;
import com.ttstd.signaling.security.TokenUtils;
import com.ttstd.signaling.store.PairingStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.Instant;

/**
 * 配对码服务：建立「被控端 → 主控端」绑定的用户友好入口。
 *
 * <p>被控端调用 {@link #generate(String)} 生成一次性配对码（明文仅回显一次，存 SHA-256 摘要）；
 * 主控端输入配对码调用 {@link #redeem(String, String)}，服务端校验后创建绑定关系。
 *
 * <p>防护：配对码有时效（默认 10 分钟）、单次使用、错误尝试上限（默认 5 次，超限即失效），
 * 且摘要存储，避免明文泄露与离线爆破。
 */
@Service
public class PairingService {

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

    private static final int MAX_ATTEMPTS = 5;
    private static final int CODE_LENGTH = 8;

    private final PairingStore pairingStore;
    private final BindingService bindingService;
    private final AuditService auditService;
    private final long ttlSeconds;

    public PairingService(PairingStore pairingStore,
                          BindingService bindingService,
                          AuditService auditService,
                          @Value("${security.pairing.ttl-seconds:600}") long ttlSeconds) {
        this.pairingStore = pairingStore;
        this.bindingService = bindingService;
        this.auditService = auditService;
        this.ttlSeconds = ttlSeconds;
    }

    /** 生成配对码，返回明文（仅此一次）。 */
    public String generate(String deviceUid) {
        if (deviceUid == null || deviceUid.isBlank()) {
            throw AuthException.badRequest("设备标识不能为空");
        }
        // 去除易混淆字符（0/O/1/I/l），仅保留 base32 风格字符
        String code = randomReadableCode(CODE_LENGTH);
        String hash = TokenUtils.sha256(code);
        Instant expiresAt = Instant.now().plusSeconds(ttlSeconds);
        PairingCode pc = new PairingCode(hash, deviceUid, expiresAt);
        pairingStore.save(pc);
        logger.info("生成配对码: device={} ttl={}s", deviceUid, ttlSeconds);
        auditService.recordDevice(deviceUid, AuditService.ACTION_PAIRING_GENERATED,
                AuditService.RESULT_SUCCESS, null, "ttl=" + ttlSeconds);
        return code;
    }

    /**
     * 兑换配对码：校验通过后创建绑定。返回绑定记录。
     */
    public DeviceBinding redeem(String code, String userId) {
        if (code == null || code.isBlank() || userId == null) {
            throw AuthException.badRequest("配对码不能为空");
        }
        String normalized = code.trim().toUpperCase();
        String hash = TokenUtils.sha256(normalized);
        PairingCode pc = pairingStore.findByCodeHash(hash)
                .orElseThrow(() -> AuthException.badRequest("配对码无效"));
        if (pc.isUsed()) {
            throw AuthException.badRequest("配对码已被使用");
        }
        if (pc.isExpired()) {
            pairingStore.delete(hash);
            throw AuthException.badRequest("配对码已过期");
        }
        if (pc.getAttempts() >= MAX_ATTEMPTS) {
            pairingStore.delete(hash);
            throw AuthException.badRequest("配对码尝试次数过多，已失效");
        }
        // 校验通过：标记已用并创建绑定（主控端 MEMBER 角色）
        pc.setUsed(true);
        pairingStore.save(pc);
        DeviceBinding binding = bindingService.bind(
                pc.getDeviceUid(), userId, DeviceBinding.BindingRole.MEMBER, null,
                "pairing:" + pc.getDeviceUid());
        logger.info("配对码兑换成功: device={} user={} binding={}", pc.getDeviceUid(), userId,
                binding.getBindingId());
        auditService.recordUser(userId, AuditService.ACTION_PAIRING_REDEEMED,
                AuditService.RESULT_SUCCESS, null, "device=" + pc.getDeviceUid());
        return binding;
    }

    private static final java.security.SecureRandom RND = new java.security.SecureRandom();

    private static String randomReadableCode(int length) {
        // base32 字符集（去除了易混淆的 0/O/1/I/L）
        final String alphabet = "23456789ABCDEFGHJKMNPQRSTUVWXYZ";
        StringBuilder sb = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            sb.append(alphabet.charAt(RND.nextInt(alphabet.length())));
        }
        return sb.toString();
    }
}
