package com.dji.sample.manage.service.password;

import com.dji.sample.manage.model.password.PasswordRule;
import com.dji.sample.manage.model.password.PasswordRuleContext;
import com.dji.sample.manage.model.password.PasswordRuleResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;

/**
 * Orchestrates all registered {@link PasswordRule} implementations in {@link org.springframework.core.annotation.Order} sequence.
 * <p>
 * Spring auto-collects every {@code PasswordRule} bean and sorts them by {@code @Order}.
 * To add, remove, or reorder a rule, simply create/delete/annotate the corresponding
 * {@code @Component} — no changes to this class are needed.
 * </p>
 *
 * <h3>Usage</h3>
 * <pre>{@code
 * PasswordRuleContext ctx = PasswordRuleContext.builder()
 *         .username(user.getUsername())
 *         .phoneNumber(user.getPhoneNumber())
 *         .build();
 * PasswordRuleResult result = passwordRuleChain.validate(rawPassword, ctx);
 * if (!result.isValid()) {
 *     throw new RuntimeException(result.getReason());
 * }
 * }</pre>
 */
@Slf4j
@Component
public class PasswordRuleChain {

    private List<PasswordRule> rules;

    /**
     * Spring injects all {@link PasswordRule} beans ordered by {@code @Order}.
     */
    public PasswordRuleChain() {

        this.rules = new ArrayList<>();

        // 1. 密码至少8位
        this.rules.add(new MinLengthPasswordRule());
        // 2. 组合规则：必须同时包含以下4 类字符中的至少 3 类 大写英文字母,小写英文字母,数字,特殊符号
//        this.rules.add(new CharacterCategoryPasswordRule());

        log.info("PasswordRuleChain initialized with {} rules",
                rules.size());
    }

    /**
     * Generate a random password that satisfies all registered rules.
     * The generated password always contains at least one character from each of the
     * 4 categories (uppercase, lowercase, digit, special) and is then shuffled,
     * guaranteeing the {@link CharacterCategoryPasswordRule} passes.
     * Length is fixed at {@value #GENERATED_PASSWORD_LENGTH} characters.
     *
     * @return a rule-compliant random plain-text password
     */
    public String generatePassword() {
        SecureRandom random = new SecureRandom();
        // Guarantee at least one character from each category
        char[] upper   = "ABCDEFGHJKLMNPQRSTUVWXYZ".toCharArray();
        char[] lower   = "abcdefghjkmnpqrstuvwxyz".toCharArray();
        char[] digits  = "23456789".toCharArray();
        char[] special = "!@#$%^&*".toCharArray();
        char[] all     = (new String(upper) + new String(lower)
                       + new String(digits) + new String(special)).toCharArray();

        StringBuilder sb = new StringBuilder(GENERATED_PASSWORD_LENGTH);
        // One guaranteed character from each category
        sb.append(upper  [random.nextInt(upper.length)]);
        sb.append(lower  [random.nextInt(lower.length)]);
        sb.append(digits [random.nextInt(digits.length)]);
        sb.append(special[random.nextInt(special.length)]);
        // Fill the remainder from all categories
        for (int i = 4; i < GENERATED_PASSWORD_LENGTH; i++) {
            sb.append(all[random.nextInt(all.length)]);
        }
        // Shuffle to avoid predictable category positions
        char[] pwd = sb.toString().toCharArray();
        for (int i = pwd.length - 1; i > 0; i--) {
            int j = random.nextInt(i + 1);
            char tmp = pwd[i]; pwd[i] = pwd[j]; pwd[j] = tmp;
        }
        return new String(pwd);
    }

    /** Length of system-generated passwords. */
    private static final int GENERATED_PASSWORD_LENGTH = 12;

    /**
     * Run the password through every rule in order.
     * Returns the first failure encountered, or {@link PasswordRuleResult#ok()} if all rules pass.
     *
     * @param password plain-text password; must not be {@code null} or blank
     * @param context  optional user context for personal-info checks
     * @return first failing {@link PasswordRuleResult}, or ok
     */
    public PasswordRuleResult validate(String password, PasswordRuleContext context) {
        if (!StringUtils.hasText(password)) {
            return PasswordRuleResult.fail("Password must not be empty");
        }
        for (PasswordRule rule : rules) {
            PasswordRuleResult result = rule.validate(password, context);
            if (!result.isValid()) {
                log.debug("Password failed rule [{}]: {}", rule.getRuleName(), result.getReason());
                return result;
            }
        }
        return PasswordRuleResult.ok();
    }

}
