package com.writeoff.module.system.service;

import com.writeoff.module.system.model.BizChangeLogInfo;
import com.writeoff.security.AuthContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

@Service
public class BizChangeLogService {
    private static final RowMapper<BizChangeLogInfo> ROW_MAPPER = (rs, n) -> new BizChangeLogInfo(
            rs.getLong("id"),
            rs.getString("biz_type"),
            rs.getLong("biz_id"),
            rs.getString("change_type"),
            rs.getString("field_code"),
            rs.getString("field_name"),
            rs.getString("before_value"),
            rs.getString("after_value"),
            rs.getObject("related_user_id") == null ? null : rs.getLong("related_user_id"),
            rs.getString("related_user_name"),
            rs.getLong("operator_user_id"),
            rs.getString("operator_user_name"),
            rs.getString("batch_id"),
            rs.getString("remark"),
            rs.getString("created_at")
    );

    private final JdbcTemplate jdbcTemplate;

    public BizChangeLogService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public List<BizChangeLogInfo> listByBiz(String bizType, Long bizId) {
        return jdbcTemplate.query(
                "SELECT id, biz_type, biz_id, change_type, field_code, field_name, before_value, after_value, " +
                        "related_user_id, related_user_name, operator_user_id, operator_user_name, batch_id, remark, " +
                        "DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s') AS created_at " +
                        "FROM biz_change_log " +
                        "WHERE tenant_id=? AND biz_type=? AND biz_id=? AND is_deleted=0 " +
                        "ORDER BY id DESC",
                ROW_MAPPER,
                tenantId(),
                safeTrim(bizType),
                bizId
        );
    }

    public void logFieldChange(String bizType,
                               Long bizId,
                               String changeType,
                               String fieldCode,
                               String fieldName,
                               Object beforeValue,
                               Object afterValue,
                               String batchId,
                               String remark) {
        if (Objects.equals(normalizeValue(beforeValue), normalizeValue(afterValue))) {
            return;
        }
        insert(
                bizType,
                bizId,
                changeType,
                fieldCode,
                fieldName,
                stringify(beforeValue),
                stringify(afterValue),
                null,
                null,
                batchId,
                remark
        );
    }

    public void logAction(String bizType, Long bizId, String changeType, String remark) {
        insert(bizType, bizId, changeType, null, null, null, null, null, null, null, remark);
    }

    public void logRelationAdd(String bizType,
                               Long bizId,
                               String changeType,
                               String fieldCode,
                               String fieldName,
                               Long relatedUserId,
                               String relatedUserName,
                               String batchId,
                               String remark) {
        insert(
                bizType,
                bizId,
                changeType,
                fieldCode,
                fieldName,
                null,
                relatedUserName,
                relatedUserId,
                relatedUserName,
                batchId,
                remark
        );
    }

    public void logRelationRemove(String bizType,
                                  Long bizId,
                                  String changeType,
                                  String fieldCode,
                                  String fieldName,
                                  Long relatedUserId,
                                  String relatedUserName,
                                  String batchId,
                                  String remark) {
        insert(
                bizType,
                bizId,
                changeType,
                fieldCode,
                fieldName,
                relatedUserName,
                null,
                relatedUserId,
                relatedUserName,
                batchId,
                remark
        );
    }

    public String newBatchId() {
        return UUID.randomUUID().toString().replace("-", "");
    }

    public String loadUserName(Long userId) {
        long id = userId == null ? 0L : userId;
        if (id <= 0L) {
            return "";
        }
        List<String> list = jdbcTemplate.query(
                "SELECT user_name FROM sys_user WHERE tenant_id=? AND id=? AND is_deleted=0 LIMIT 1",
                (rs, n) -> rs.getString("user_name"),
                tenantId(),
                id
        );
        return list.isEmpty() ? "" : safeTrim(list.get(0));
    }

    public List<UserRef> loadUserRefs(List<Long> userIds) {
        List<Long> validIds = new ArrayList<Long>();
        for (Long userId : userIds) {
            long id = userId == null ? 0L : userId;
            if (id > 0L && !validIds.contains(id)) {
                validIds.add(id);
            }
        }
        if (validIds.isEmpty()) {
            return new ArrayList<UserRef>();
        }
        StringBuilder sql = new StringBuilder("SELECT id, user_name FROM sys_user WHERE tenant_id=? AND is_deleted=0 AND id IN (");
        List<Object> args = new ArrayList<Object>();
        args.add(tenantId());
        for (int i = 0; i < validIds.size(); i++) {
            if (i > 0) {
                sql.append(",");
            }
            sql.append("?");
            args.add(validIds.get(i));
        }
        sql.append(")");
        return jdbcTemplate.query(
                sql.toString(),
                (rs, n) -> new UserRef(rs.getLong("id"), safeTrim(rs.getString("user_name"))),
                args.toArray()
        );
    }

    private void insert(String bizType,
                        Long bizId,
                        String changeType,
                        String fieldCode,
                        String fieldName,
                        String beforeValue,
                        String afterValue,
                        Long relatedUserId,
                        String relatedUserName,
                        String batchId,
                        String remark) {
        jdbcTemplate.update(
                "INSERT INTO biz_change_log (tenant_id, biz_type, biz_id, change_type, field_code, field_name, before_value, after_value, " +
                        "related_user_id, related_user_name, operator_user_id, operator_user_name, batch_id, remark, is_deleted) " +
                        "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)",
                tenantId(),
                safeTrim(bizType),
                bizId == null ? 0L : bizId,
                safeTrim(changeType),
                emptyToNull(fieldCode),
                emptyToNull(fieldName),
                truncate(beforeValue, 2000),
                truncate(afterValue, 2000),
                relatedUserId,
                emptyToNull(relatedUserName),
                safeUserId(),
                emptyToNull(loadUserName(safeUserId())),
                emptyToNull(batchId),
                truncate(remark, 500)
        );
    }

    private String normalizeValue(Object value) {
        return stringify(value);
    }

    private String stringify(Object value) {
        if (value == null) {
            return null;
        }
        String text = String.valueOf(value).trim();
        return text.isEmpty() ? null : text;
    }

    private String truncate(String value, int maxLength) {
        String normalized = stringify(value);
        if (normalized == null || normalized.length() <= maxLength) {
            return normalized;
        }
        return normalized.substring(0, maxLength);
    }

    private String emptyToNull(String value) {
        String normalized = safeTrim(value);
        return normalized.isEmpty() ? null : normalized;
    }

    private String safeTrim(String value) {
        return value == null ? "" : value.trim();
    }

    private Long tenantId() {
        return AuthContext.requireTenantId();
    }

    private Long safeUserId() {
        Long userId = AuthContext.userId();
        return userId == null ? 0L : userId;
    }

    public static class UserRef {
        private final Long userId;
        private final String userName;

        public UserRef(Long userId, String userName) {
            this.userId = userId;
            this.userName = userName;
        }

        public Long getUserId() {
            return userId;
        }

        public String getUserName() {
            return userName;
        }
    }
}
