package com.ttstd.signaling.model;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 主控端用户账号（同时作为 JPA 实体持久化到 MySQL）。
 */
@Entity
@Table(name = "app_user")
public class UserAccount {

    /** 密码历史保留条数，用于禁止复用近期密码 */
    public static final int PASSWORD_HISTORY_SIZE = 5;

    @Id
    @Column(name = "user_id", length = 64, nullable = false)
    private String userId;

    @Column(name = "username", length = 64, unique = true, nullable = false)
    private String username;

    @Column(name = "password_hash", length = 100, nullable = false)
    private String passwordHash;

    @Column(name = "created_at", nullable = false)
    private Instant createdAt;

    @Enumerated(EnumType.STRING)
    @Column(name = "status", length = 16, nullable = false)
    private AccountStatus status = AccountStatus.ACTIVE;

    /** 封禁到期时间（仅 SUSPENDED 有效），为空表示不自动恢复 */
    @Column(name = "status_until")
    private Instant statusUntil;

    @Column(name = "status_reason", length = 255)
    private String statusReason;

    /**
     * 凭据版本号：递增后所有已签发的 token 立即失效。
     * 用于封禁、改密、全端下线等场景。
     */
    @Column(name = "token_version", nullable = false)
    private long tokenVersion = 1L;

    /** 连续登录失败次数与锁定截止时间，用于防爆破 */
    @Column(name = "failed_attempts", nullable = false)
    private int failedAttempts;

    @Column(name = "locked_until")
    private Instant lockedUntil;

    @Column(name = "last_login_at")
    private Instant lastLoginAt;

    @Column(name = "last_login_ip", length = 64)
    private String lastLoginIp;

    /** TOTP 密钥（Base32）。为空表示未生成；生成后需 totpEnabled=true 才生效 */
    @Column(name = "totp_secret", length = 64)
    private String totpSecret;

    /** TOTP 是否已完成绑定并启用 */
    @Column(name = "totp_enabled", nullable = false)
    private boolean totpEnabled;

    /**
     * 近期密码哈希历史，以换行分隔，用于禁止复用最近 {@value #PASSWORD_HISTORY_SIZE} 个密码。
     * <p>存为单列可避免额外建表，条数很少（默认 5），不影响性能。
     */
    @Column(name = "password_history", length = 1024)
    private String passwordHistory;

    /** 密码最后修改时间，用于密码过期策略 */
    @Column(name = "password_changed_at")
    private Instant passwordChangedAt;

    protected UserAccount() {
        this.userId = null;
        this.username = null;
        this.passwordHash = null;
        this.createdAt = Instant.now();
    }

    public UserAccount(String userId, String username, String passwordHash) {
        this.userId = userId;
        this.username = username;
        this.passwordHash = passwordHash;
        this.createdAt = Instant.now();
    }

    public String getUserId() { return userId; }
    public String getUsername() { return username; }
    public Instant getCreatedAt() { return createdAt; }

    public String getPasswordHash() { return passwordHash; }
    public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }

    public AccountStatus getStatus() { return status; }
    public void setStatus(AccountStatus status) { this.status = status; }

    public Instant getStatusUntil() { return statusUntil; }
    public void setStatusUntil(Instant statusUntil) { this.statusUntil = statusUntil; }

    public String getStatusReason() { return statusReason; }
    public void setStatusReason(String statusReason) { this.statusReason = statusReason; }

    public long getTokenVersion() { return tokenVersion; }
    public void setTokenVersion(long tokenVersion) { this.tokenVersion = tokenVersion; }

    public int getFailedAttempts() { return failedAttempts; }
    public void setFailedAttempts(int failedAttempts) { this.failedAttempts = failedAttempts; }

    public Instant getLockedUntil() { return lockedUntil; }
    public void setLockedUntil(Instant lockedUntil) { this.lockedUntil = lockedUntil; }

    public Instant getLastLoginAt() { return lastLoginAt; }
    public void setLastLoginAt(Instant lastLoginAt) { this.lastLoginAt = lastLoginAt; }

    public String getLastLoginIp() { return lastLoginIp; }
    public void setLastLoginIp(String lastLoginIp) { this.lastLoginIp = lastLoginIp; }

    public String getTotpSecret() { return totpSecret; }
    public void setTotpSecret(String totpSecret) { this.totpSecret = totpSecret; }

    public boolean isTotpEnabled() { return totpEnabled; }
    public void setTotpEnabled(boolean totpEnabled) { this.totpEnabled = totpEnabled; }

    public String getPasswordHistory() { return passwordHistory; }
    public void setPasswordHistory(String passwordHistory) { this.passwordHistory = passwordHistory; }

    public Instant getPasswordChangedAt() { return passwordChangedAt; }
    public void setPasswordChangedAt(Instant passwordChangedAt) { this.passwordChangedAt = passwordChangedAt; }

    /** 返回历史密码哈希列表（最新在前）。 */
    public List<String> passwordHistoryList() {
        if (passwordHistory == null || passwordHistory.isBlank()) {
            return List.of();
        }
        return Arrays.stream(passwordHistory.split("\n"))
                .filter(s -> !s.isBlank())
                .toList();
    }

    /**
     * 将当前密码哈希追加进历史，并裁剪到 {@value #PASSWORD_HISTORY_SIZE} 条。
     */
    public void pushPasswordHistory(String hash) {
        if (hash == null || hash.isBlank()) {
            return;
        }
        List<String> history = new ArrayList<>();
        history.add(hash);
        for (String old : passwordHistoryList()) {
            if (history.size() >= PASSWORD_HISTORY_SIZE) {
                break;
            }
            if (!old.equals(hash)) {
                history.add(old);
            }
        }
        this.passwordHistory = String.join("\n", history);
    }

    /**
     * 判断账号当前是否可用。SUSPENDED 且已过期时视为可用（由调用方负责回写状态）。
     */
    public boolean isUsable() {
        if (status == AccountStatus.BANNED) {
            return false;
        }
        if (status == AccountStatus.SUSPENDED) {
            return statusUntil != null && Instant.now().isAfter(statusUntil);
        }
        return true;
    }

    /** 当前是否处于登录失败锁定期。 */
    public boolean isLocked() {
        return lockedUntil != null && Instant.now().isBefore(lockedUntil);
    }
}
