package com.ttstd.signaling.service;

import com.ttstd.signaling.model.AuditLog;
import com.ttstd.signaling.repository.AuditLogRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * 安全审计日志。
 *
 * <p>记录登录、激活、封禁、踢线等安全敏感操作，用于事后追溯。
 * 记录持久化到 {@code audit_log} 表，同时写入 SLF4J 便于日志采集。
 *
 * <p>写入前会对敏感信息做脱敏与长度截断，避免日志泄露凭据。
 */
@Service
public class AuditService {

    private static final Logger auditLogger = LoggerFactory.getLogger("AUDIT");
    private static final Logger logger = LoggerFactory.getLogger(AuditService.class);

    /** 审计动作常量 */
    public static final String ACTION_LOGIN = "LOGIN";
    public static final String ACTION_LOGIN_FAILED = "LOGIN_FAILED";
    public static final String ACTION_LOGOUT = "LOGOUT";
    public static final String ACTION_REGISTER = "REGISTER";
    public static final String ACTION_REFRESH = "REFRESH";
    public static final String ACTION_REFRESH_REUSE = "REFRESH_TOKEN_REUSE";
    public static final String ACTION_PASSWORD_CHANGED = "PASSWORD_CHANGED";
    public static final String ACTION_ACCOUNT_LOCKED = "ACCOUNT_LOCKED";
    public static final String ACTION_TOTP_ENABLED = "TOTP_ENABLED";
    public static final String ACTION_TOTP_DISABLED = "TOTP_DISABLED";
    public static final String ACTION_TOTP_FAILED = "TOTP_FAILED";
    public static final String ACTION_BAN = "BAN";
    public static final String ACTION_UNBAN = "UNBAN";
    public static final String ACTION_KICK = "KICK";
    public static final String ACTION_USER_CREATED = "USER_CREATED";
    public static final String ACTION_USER_DELETED = "USER_DELETED";
    public static final String ACTION_PROVISION = "DEVICE_PROVISION";
    public static final String ACTION_DEVICE_TOKEN = "DEVICE_TOKEN";
    public static final String ACTION_DEVICE_DISABLED = "DEVICE_DISABLED";
    public static final String ACTION_DEVICE_ENABLED = "DEVICE_ENABLED";
    public static final String ACTION_DEVICE_CREATED = "DEVICE_CREATED";
    public static final String ACTION_DEVICE_DELETED = "DEVICE_DELETED";
    public static final String ACTION_DEVICE_KICK = "DEVICE_KICK";
    public static final String ACTION_BIND = "DEVICE_BIND";
    public static final String ACTION_UNBIND = "DEVICE_UNBIND";
    public static final String ACTION_BLACKLIST_ADD = "DEVICE_BLACKLIST_ADD";
    public static final String ACTION_BLACKLIST_REMOVE = "DEVICE_BLACKLIST_REMOVE";
    public static final String ACTION_ALLOWLIST_ADD = "DEVICE_ALLOWLIST_ADD";
    public static final String ACTION_ALLOWLIST_REMOVE = "DEVICE_ALLOWLIST_REMOVE";
    public static final String ACTION_OFFER_BLOCKED = "OFFER_BLOCKED";
    public static final String ACTION_PAIRING_GENERATED = "PAIRING_GENERATED";
    public static final String ACTION_PAIRING_REDEEMED = "PAIRING_REDEEMED";

    public static final String RESULT_SUCCESS = "SUCCESS";
    public static final String RESULT_FAILURE = "FAILURE";

    /**
     * 单条审计记录的对外视图。
     */
    public record AuditEntry(
            long id,
            String actorType,
            String actorId,
            String action,
            String targetId,
            String result,
            String ip,
            String detail,
            long timestamp) {

        static AuditEntry from(AuditLog log) {
            return new AuditEntry(
                    log.getId() == null ? 0L : log.getId(),
                    log.getActorType(),
                    log.getActorId(),
                    log.getAction(),
                    log.getTargetId(),
                    log.getResult(),
                    log.getIp(),
                    log.getDetail(),
                    log.getCreatedAt().toEpochMilli());
        }
    }

    private final AuditLogRepository repository;

    /** 带持久化的标准构造（由 Spring 注入）。 */
    public AuditService(AuditLogRepository repository) {
        this.repository = repository;
    }

    /**
     * 无参构造：用于不依赖数据库的单元测试。此时审计仅写入 SLF4J，不落库。
     * 与线上"写库失败仅记日志"的容错策略保持一致。
     */
    public AuditService() {
        this.repository = null;
    }

    /**
     * 写入一条审计记录。
     *
     * <p>使用独立事务，确保调用方业务回滚时审计记录仍然保留；
     * 审计写库失败不得影响主流程，因此异常仅记录日志。
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void record(String actorType, String actorId, String action,
                       String targetId, String result, String ip, String detail) {
        String safeDetail = truncate(detail);
        auditLogger.info("actor={}:{} action={} target={} result={} ip={} detail={}",
                actorType, actorId, action, targetId, result, ip, safeDetail);
        try {
            if (repository != null) {
                repository.save(new AuditLog(actorType, actorId, action, targetId, result, ip, safeDetail));
            }
        } catch (RuntimeException e) {
            logger.warn("审计日志写入失败: action={} actor={} 原因={}", action, actorId, e.toString());
        }
    }

    public void recordUser(String userId, String action, String result, String ip, String detail) {
        record("USER", userId, action, null, result, ip, detail);
    }

    public void recordDevice(String deviceUid, String action, String result, String ip, String detail) {
        record("DEVICE", deviceUid, action, null, result, ip, detail);
    }

    public void recordAdmin(String adminId, String action, String targetId, String detail) {
        record("ADMIN", adminId, action, targetId, RESULT_SUCCESS, null, detail);
    }

    /** 查询最近的审计记录。 */
    @Transactional(readOnly = true)
    public List<AuditEntry> recent(int limit) {
        return search(null, null, null, 0, limit).getContent();
    }

    /** 按主体过滤审计记录。 */
    @Transactional(readOnly = true)
    public List<AuditEntry> byActor(String actorId, int limit) {
        return search(actorId, null, null, 0, limit).getContent();
    }

    /**
     * 条件分页查询，空白参数视为不过滤。
     */
    @Transactional(readOnly = true)
    public Page<AuditEntry> search(String actorId, String action, String result, int page, int size) {
        if (repository == null) {
            return org.springframework.data.domain.Page.empty();
        }
        Page<AuditLog> found = repository.search(
                blankToNull(actorId), blankToNull(action), blankToNull(result),
                PageRequest.of(Math.max(page, 0), Math.min(Math.max(size, 1), 1000)));
        return found.map(AuditEntry::from);
    }

    /** 清理早于指定时间的历史审计日志。 */
    @Transactional
    public int purgeBefore(Instant before) {
        return repository.deleteOlderThan(before);
    }

    private static String blankToNull(String value) {
        return value == null || value.isBlank() ? null : value;
    }

    private static String truncate(String detail) {
        if (detail == null) {
            return null;
        }
        return detail.length() > 512 ? detail.substring(0, 512) : detail;
    }
}
