package com.writeoff.module.system.service;

import com.writeoff.common.api.PageResult;
import com.writeoff.common.exception.BusinessException;
import com.writeoff.module.file.service.OssService;
import com.writeoff.module.system.dto.CreateTenantAdminRequest;
import com.writeoff.module.system.dto.CreateTenantRequest;
import com.writeoff.module.system.model.TenantInfo;
import com.writeoff.module.notification.provider.NotificationChannelProvider;
import com.writeoff.module.notification.provider.NotificationSendResult;
import com.writeoff.security.AuthContext;
import com.writeoff.security.PasswordCodecService;
import com.writeoff.security.PasswordPolicyService;
import com.writeoff.security.PasswordSetupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

@Service
public class TenantService {
    private final JdbcTemplate jdbcTemplate;
    private final OssService ossService;
    private final Map<String, NotificationChannelProvider> providerMap;
    private final PasswordPolicyService passwordPolicyService;
    private final PasswordCodecService passwordCodecService;
    @Autowired
    private PasswordSetupService passwordSetupService;
    private final String tenantAdminMailSubjectTemplate;
    private final String tenantAdminMailBodyTemplate;

    private static final RowMapper<TenantInfo> TENANT_ROW_MAPPER = (rs, n) -> new TenantInfo(
            rs.getLong("id"),
            rs.getString("tenant_code"),
            rs.getString("tenant_name"),
            rs.getString("logo_url"),
            rs.getString("status"),
            rs.getString("created_at")
    );

    public TenantService(JdbcTemplate jdbcTemplate,
                         OssService ossService,
                         List<NotificationChannelProvider> providers,
                         PasswordPolicyService passwordPolicyService,
                         PasswordCodecService passwordCodecService,
                         @Value("${app.notification.tenant-admin-mail-subject-template:租户管理员账号通知}") String tenantAdminMailSubjectTemplate,
                         @Value("${app.notification.tenant-admin-mail-body-template:操作类型：{actionCn}\\n租户名称：{tenantName}\\n租户编码：{tenantCode}\\n登录地址：{loginPath}\\n管理员账号：{phone}\\n管理员密码：{password}\\n请首次登录后立即修改密码。}") String tenantAdminMailBodyTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        this.ossService = ossService;
        this.providerMap = new HashMap<String, NotificationChannelProvider>();
        this.passwordPolicyService = passwordPolicyService;
        this.passwordCodecService = passwordCodecService;
        this.tenantAdminMailSubjectTemplate = tenantAdminMailSubjectTemplate;
        this.tenantAdminMailBodyTemplate = tenantAdminMailBodyTemplate;
        if (providers != null) {
            for (NotificationChannelProvider provider : providers) {
                providerMap.put(provider.channel().toUpperCase(), provider);
            }
        }
    }

    public PageResult<TenantInfo> list() {
        List<TenantInfo> list = jdbcTemplate.query(
                "SELECT id, tenant_code, tenant_name, logo_url, status, DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s') AS created_at " +
                        "FROM tenant WHERE is_deleted=0 ORDER BY id DESC",
                TENANT_ROW_MAPPER
        );
        return new PageResult<TenantInfo>(list, list.size(), 1, 50);
    }

    public PageResult<TenantInfo> listCurrentTenant() {
        TenantInfo currentTenant = findById(AuthContext.requireTenantId());
        return new PageResult<TenantInfo>(java.util.Collections.singletonList(currentTenant), 1, 1, 1);
    }

    public TenantInfo create(CreateTenantRequest request) {
        Integer exists = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM tenant WHERE tenant_code=?",
                Integer.class,
                request.getTenantCode().trim()
        );
        if (exists != null && exists > 0) {
            throw new BusinessException(10001, "租户编码已存在");
        }
        jdbcTemplate.update(
                "INSERT INTO tenant (tenant_code, tenant_name, logo_url, status, is_deleted, created_by, updated_by) VALUES (?, ?, ?, 'ENABLED', 0, ?, ?)",
                request.getTenantCode().trim(),
                request.getTenantName().trim(),
                normalizeNullable(request.getLogoUrl()),
                safeUserId(),
                safeUserId()
        );
        Long id = jdbcTemplate.queryForObject("SELECT IFNULL(MAX(id), 0) FROM tenant", Long.class);
        long tenantId = id == null ? 0L : id;
        initTenantBaseline(tenantId);
        return findById(tenantId);
    }

    public java.util.Map<String, Object> initTenantBaseline(Long tenantId) {
        assertExists(tenantId);
        int menuCount = ensureTenantMenusFromTemplate(tenantId);
        int roleCount = ensureTenantRolesFromTemplate(tenantId);
        int rolePermCount = 0;
        int roleMenuCount = 0;
        List<Map<String, Object>> templateRoles = jdbcTemplate.queryForList(
                "SELECT role_code FROM role WHERE tenant_id=1 AND is_deleted=0"
        );
        for (Map<String, Object> row : templateRoles) {
            String roleCode = String.valueOf(row.get("role_code"));
            Long targetRoleId = ensureTenantRole(tenantId, roleCode);
            rolePermCount += ensureRolePermissionsFromTemplate(tenantId, targetRoleId, roleCode);
            roleMenuCount += ensureRoleMenusFromTemplate(tenantId, targetRoleId, roleCode);
        }
        java.util.Map<String, Object> data = new java.util.LinkedHashMap<String, Object>();
        data.put("tenantId", tenantId);
        data.put("menuInitialized", menuCount);
        data.put("roleInitialized", roleCount);
        data.put("rolePermissionInitialized", rolePermCount);
        data.put("roleMenuInitialized", roleMenuCount);
        return data;
    }

    public void enable(Long tenantId) {
        assertExists(tenantId);
        jdbcTemplate.update("UPDATE tenant SET status='ENABLED', updated_by=?, updated_at=CURRENT_TIMESTAMP WHERE id=?", safeUserId(), tenantId);
    }

    public void disable(Long tenantId) {
        assertExists(tenantId);
        jdbcTemplate.update("UPDATE tenant SET status='DISABLED', updated_by=?, updated_at=CURRENT_TIMESTAMP WHERE id=?", safeUserId(), tenantId);
    }

    public TenantInfo updateTenant(Long tenantId, String tenantName, String logoUrl) {
        assertExists(tenantId);
        if (tenantName == null || tenantName.trim().isEmpty()) {
            throw new BusinessException(10001, "租户名称不能为空");
        }
        jdbcTemplate.update(
                "UPDATE tenant SET tenant_name=?, logo_url=?, updated_by=?, updated_at=CURRENT_TIMESTAMP WHERE id=? AND is_deleted=0",
                tenantName.trim(),
                normalizeNullable(logoUrl),
                safeUserId(),
                tenantId
        );
        return findById(tenantId);
    }

    public void softDelete(Long tenantId) {
        assertExists(tenantId);
        // 检查是否有活跃业务数据
        Integer activeProjects = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM project WHERE tenant_id=? AND is_deleted=0 AND status NOT IN ('ARCHIVED', 'TERMINATED')",
                Integer.class,
                tenantId
        );
        if (activeProjects != null && activeProjects > 0) {
            throw new BusinessException(10001, "该租户下存在活跃项目，请先归档或终止所有项目后再删除");
        }
        jdbcTemplate.update(
                "UPDATE tenant SET is_deleted=1, updated_by=?, updated_at=CURRENT_TIMESTAMP WHERE id=?",
                safeUserId(),
                tenantId
        );
    }

    public java.util.Map<String, Object> createTenantAdmin(Long tenantId, CreateTenantAdminRequest request) {
        assertExists(tenantId);
        String roleCode = request.getRoleCode() == null || request.getRoleCode().trim().isEmpty()
                ? "TENANT_ADMIN"
                : request.getRoleCode().trim();
        Long roleId = ensureTenantRole(tenantId, roleCode);
        ensureRolePermissionsFromTemplate(tenantId, roleId, roleCode);
        ensureRoleMenusFromTemplate(tenantId, roleId, roleCode);
        Long existingAdminUserId = findTenantAdminUserId(tenantId, roleCode);
        String passwordHash = passwordCodecService.encode(generateTemporaryPassword());

        List<Map<String, Object>> samePhoneUsers = jdbcTemplate.queryForList(
                "SELECT id FROM sys_user WHERE tenant_id=? AND phone=? AND is_deleted=0 LIMIT 1",
                tenantId,
                request.getPhone().trim()
        );
        if (!samePhoneUsers.isEmpty()) {
            Long phoneUserId = ((Number) samePhoneUsers.get(0).get("id")).longValue();
            if (existingAdminUserId == null || !phoneUserId.equals(existingAdminUserId)) {
                throw new BusinessException(10001, "该租户下手机号已存在");
            }
        }

        long uid;
        String action;
        if (existingAdminUserId == null) {
            String tenantSwitchAccountKey = resolveTenantSwitchAccountKeyByPhone(request.getPhone().trim(), null);
            jdbcTemplate.update(
                    "INSERT INTO sys_user (tenant_id, user_name, phone, email, password_hash, tenant_switch_account_key, status, valid_from, valid_to, is_deleted, created_by, updated_by) " +
                            "VALUES (?, ?, ?, ?, ?, ?, 'ENABLED', NOW(), '2099-12-31 23:59:59', 0, ?, ?)",
                    tenantId,
                    request.getUserName().trim(),
                    request.getPhone().trim(),
                    request.getEmail().trim(),
                    passwordHash,
                    tenantSwitchAccountKey,
                    safeUserId(),
                    safeUserId()
            );
            Long userId = jdbcTemplate.queryForObject(
                    "SELECT IFNULL(MAX(id), 0) FROM sys_user WHERE tenant_id=?",
                    Long.class,
                    tenantId
            );
            uid = userId == null ? 0L : userId;
            action = "CREATED";
        } else {
            uid = existingAdminUserId;
            String tenantSwitchAccountKey = loadTenantSwitchAccountKey(tenantId, uid);
            if (tenantSwitchAccountKey == null || tenantSwitchAccountKey.trim().isEmpty()) {
                tenantSwitchAccountKey = resolveTenantSwitchAccountKeyByPhone(request.getPhone().trim(), uid);
            }
            jdbcTemplate.update(
                    "UPDATE sys_user SET user_name=?, phone=?, email=?, password_hash=?, tenant_switch_account_key=?, status='ENABLED', valid_to='2099-12-31 23:59:59', updated_by=?, updated_at=CURRENT_TIMESTAMP " +
                            "WHERE tenant_id=? AND id=?",
                    request.getUserName().trim(),
                    request.getPhone().trim(),
                    request.getEmail().trim(),
                    passwordHash,
                    tenantSwitchAccountKey,
                    safeUserId(),
                    tenantId,
                    uid
            );
            action = "UPDATED";
        }

        Integer relationExists = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM user_role WHERE tenant_id=? AND user_id=? AND role_id=?",
                Integer.class,
                tenantId,
                uid,
                roleId
        );
        if (relationExists == null || relationExists == 0) {
            Long nextUserRoleId = jdbcTemplate.queryForObject("SELECT IFNULL(MAX(id), 0) + 1 FROM user_role", Long.class);
            jdbcTemplate.update(
                    "INSERT INTO user_role (id, tenant_id, user_id, role_id) VALUES (?, ?, ?, ?)",
                    nextUserRoleId == null ? 1L : nextUserRoleId,
                    tenantId,
                    uid,
                    roleId
            );
        }
        jdbcTemplate.update(
                "INSERT INTO user_role_history (tenant_id, user_id, old_role_id, new_role_id, action_type, action_reason, created_by) VALUES (?, ?, NULL, ?, 'ASSIGN', ?, ?)",
                tenantId,
                uid,
                roleId,
                "平台设置租户管理员",
                safeUserId()
        );

        String setupLink = passwordSetupService.issueTenantAdminSetupLink(tenantId, uid, safeUserId());
        sendTenantAdminMail(tenantId, request, action, setupLink);

        java.util.Map<String, Object> data = new java.util.LinkedHashMap<String, Object>();
        data.put("tenantId", tenantId);
        data.put("userId", uid);
        data.put("roleId", roleId);
        data.put("roleCode", roleCode);
        data.put("action", action);
        return data;
    }

    public java.util.Map<String, Object> getTenantAdmin(Long tenantId, String roleCode) {
        assertExists(tenantId);
        String normalizedRoleCode = roleCode == null || roleCode.trim().isEmpty() ? "TENANT_ADMIN" : roleCode.trim();
        List<Map<String, Object>> rows = jdbcTemplate.queryForList(
                "SELECT u.id, u.user_name, u.phone, u.email, r.role_code " +
                        "FROM user_role ur " +
                        "JOIN role r ON ur.role_id=r.id " +
                        "JOIN sys_user u ON ur.user_id=u.id " +
                        "WHERE ur.tenant_id=? AND r.tenant_id=? AND r.role_code=? AND r.is_deleted=0 AND u.is_deleted=0 " +
                        "ORDER BY ur.id DESC LIMIT 1",
                tenantId,
                tenantId,
                normalizedRoleCode
        );
        if (rows.isEmpty()) {
            return null;
        }
        Map<String, Object> row = rows.get(0);
        java.util.Map<String, Object> data = new java.util.LinkedHashMap<String, Object>();
        data.put("userId", ((Number) row.get("id")).longValue());
        data.put("userName", row.get("user_name"));
        data.put("phone", row.get("phone"));
        data.put("email", row.get("email"));
        data.put("roleCode", row.get("role_code"));
        return data;
    }

    public Map<String, Object> presignLogoUpload(String fileName, String contentType) {
        String name = fileName == null ? "" : fileName.trim();
        if (name.isEmpty()) {
            throw new BusinessException(10001, "文件名不能为空");
        }
        String ext = "";
        int idx = name.lastIndexOf('.');
        if (idx > 0 && idx < name.length() - 1) {
            ext = "." + name.substring(idx + 1).toLowerCase();
        }
        String normalizedContentType = normalizeContentType(contentType);
        String objectKey = "tenant/logo/" + safeUserId() + "/" + UUID.randomUUID().toString().replace("-", "") + ext;
        String uploadUrl = ossService.generateUploadUrl(objectKey, normalizedContentType);
        Map<String, Object> data = new LinkedHashMap<String, Object>();
        data.put("objectKey", objectKey);
        data.put("uploadUrl", uploadUrl);
        data.put("contentType", normalizedContentType);
        data.put("method", "PUT");
        return data;
    }

    private Long ensureTenantRole(Long tenantId, String roleCode) {
        java.util.List<Long> roleIds = jdbcTemplate.queryForList(
                "SELECT id FROM role WHERE tenant_id=? AND role_code=? AND is_deleted=0 LIMIT 1",
                Long.class,
                tenantId,
                roleCode
        );
        if (!roleIds.isEmpty()) {
            return roleIds.get(0);
        }
        java.util.List<java.util.Map<String, Object>> templateRows = jdbcTemplate.queryForList(
                "SELECT id, role_name FROM role WHERE tenant_id=1 AND role_code=? AND is_deleted=0 LIMIT 1",
                roleCode
        );
        String roleName = "单位管理员";
        if (!templateRows.isEmpty()) {
            java.util.Map<String, Object> row = templateRows.get(0);
            roleName = String.valueOf(row.get("role_name"));
        }
        Long nextRoleId = jdbcTemplate.queryForObject("SELECT IFNULL(MAX(id), 0) + 1 FROM role", Long.class);
        long roleId = nextRoleId == null ? 1L : nextRoleId;
        jdbcTemplate.update(
                "INSERT INTO role (id, tenant_id, role_code, role_name, status, is_deleted, created_by, updated_by) VALUES (?, ?, ?, ?, 'ENABLED', 0, ?, ?)",
                roleId,
                tenantId,
                roleCode,
                roleName,
                safeUserId(),
                safeUserId()
        );
        return roleId;
    }

    private int ensureRolePermissionsFromTemplate(Long tenantId, Long roleId, String roleCode) {
        List<Map<String, Object>> templates = jdbcTemplate.queryForList(
                "SELECT id FROM role WHERE tenant_id=1 AND role_code=? AND is_deleted=0 LIMIT 1",
                roleCode
        );
        if (templates.isEmpty()) {
            return 0;
        }
        Long templateRoleId = ((Number) templates.get(0).get("id")).longValue();
        List<Long> permIds = jdbcTemplate.queryForList(
                "SELECT permission_id FROM role_permission WHERE tenant_id=1 AND role_id=?",
                Long.class,
                templateRoleId
        );
        int inserted = 0;
        if (!permIds.isEmpty()) {
            Long nextRolePermId = jdbcTemplate.queryForObject("SELECT IFNULL(MAX(id), 0) + 1 FROM role_permission", Long.class);
            long currentPermId = nextRolePermId == null ? 1L : nextRolePermId;
            for (Long permissionId : permIds) {
                Integer exists = jdbcTemplate.queryForObject(
                        "SELECT COUNT(1) FROM role_permission WHERE tenant_id=? AND role_id=? AND permission_id=?",
                        Integer.class,
                        tenantId,
                        roleId,
                        permissionId
                );
                if (exists != null && exists > 0) {
                    continue;
                }
                jdbcTemplate.update(
                        "INSERT INTO role_permission (id, tenant_id, role_id, permission_id) VALUES (?, ?, ?, ?)",
                        currentPermId++,
                        tenantId,
                        roleId,
                        permissionId
                );
                inserted++;
            }
        }
        return inserted;
    }

    private int ensureRoleMenusFromTemplate(Long tenantId, Long roleId, String roleCode) {
        List<Map<String, Object>> templates = jdbcTemplate.queryForList(
                "SELECT id FROM role WHERE tenant_id=1 AND role_code=? AND is_deleted=0 LIMIT 1",
                roleCode
        );
        if (templates.isEmpty()) {
            return 0;
        }
        Long templateRoleId = ((Number) templates.get(0).get("id")).longValue();
        List<Map<String, Object>> templateMenus = jdbcTemplate.queryForList(
                "SELECT m.menu_code FROM role_menu rm " +
                        "JOIN menu m ON rm.tenant_id=m.tenant_id AND rm.menu_id=m.id " +
                        "WHERE rm.tenant_id=1 AND rm.role_id=? AND m.is_deleted=0",
                templateRoleId
        );
        int inserted = 0;
        if (!templateMenus.isEmpty()) {
            Long nextRoleMenuId = jdbcTemplate.queryForObject("SELECT IFNULL(MAX(id), 0) + 1 FROM role_menu", Long.class);
            long currentMenuId = nextRoleMenuId == null ? 1L : nextRoleMenuId;
            for (Map<String, Object> row : templateMenus) {
                String menuCode = String.valueOf(row.get("menu_code"));
                List<Long> targetMenuIds = jdbcTemplate.queryForList(
                        "SELECT id FROM menu WHERE tenant_id=? AND menu_code=? AND is_deleted=0 LIMIT 1",
                        Long.class,
                        tenantId,
                        menuCode
                );
                if (targetMenuIds.isEmpty()) {
                    continue;
                }
                Long targetMenuId = targetMenuIds.get(0);
                Integer exists = jdbcTemplate.queryForObject(
                        "SELECT COUNT(1) FROM role_menu WHERE tenant_id=? AND role_id=? AND menu_id=?",
                        Integer.class,
                        tenantId,
                        roleId,
                        targetMenuId
                );
                if (exists != null && exists > 0) {
                    continue;
                }
                jdbcTemplate.update(
                        "INSERT INTO role_menu (id, tenant_id, role_id, menu_id) VALUES (?, ?, ?, ?)",
                        currentMenuId++,
                        tenantId,
                        roleId,
                        targetMenuId
                );
                inserted++;
            }
        }
        return inserted;
    }

    private int ensureTenantMenusFromTemplate(Long tenantId) {
        List<Map<String, Object>> templateMenus = jdbcTemplate.queryForList(
                "SELECT menu_code, menu_name, route_path, permission_code, sort_no, status " +
                        "FROM menu WHERE tenant_id=1 AND is_deleted=0"
        );
        if (templateMenus.isEmpty()) {
            return 0;
        }
        int inserted = 0;
        for (Map<String, Object> row : templateMenus) {
            String menuCode = String.valueOf(row.get("menu_code"));
            Integer exists = jdbcTemplate.queryForObject(
                    "SELECT COUNT(1) FROM menu WHERE tenant_id=? AND menu_code=? AND is_deleted=0",
                    Integer.class,
                    tenantId,
                    menuCode
            );
            if (exists != null && exists > 0) {
                continue;
            }
            jdbcTemplate.update(
                    "INSERT INTO menu (tenant_id, menu_code, menu_name, route_path, permission_code, sort_no, status, is_deleted, created_by, updated_by) " +
                            "VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?)",
                    tenantId,
                    menuCode,
                    String.valueOf(row.get("menu_name")),
                    String.valueOf(row.get("route_path")),
                    row.get("permission_code"),
                    ((Number) row.get("sort_no")).intValue(),
                    String.valueOf(row.get("status")),
                    safeUserId(),
                    safeUserId()
            );
            inserted++;
        }
        return inserted;
    }

    private int ensureTenantRolesFromTemplate(Long tenantId) {
        List<Map<String, Object>> templateRoles = jdbcTemplate.queryForList(
                "SELECT role_code FROM role WHERE tenant_id=1 AND is_deleted=0"
        );
        if (templateRoles.isEmpty()) {
            return 0;
        }
        int inserted = 0;
        for (Map<String, Object> row : templateRoles) {
            String roleCode = String.valueOf(row.get("role_code"));
            List<Long> exists = jdbcTemplate.queryForList(
                    "SELECT id FROM role WHERE tenant_id=? AND role_code=? AND is_deleted=0 LIMIT 1",
                    Long.class,
                    tenantId,
                    roleCode
            );
            if (exists.isEmpty()) {
                ensureTenantRole(tenantId, roleCode);
                inserted++;
            }
        }
        return inserted;
    }

    private Long findTenantAdminUserId(Long tenantId, String roleCode) {
        List<Long> ids = jdbcTemplate.queryForList(
                "SELECT u.id FROM user_role ur " +
                        "JOIN role r ON ur.role_id=r.id " +
                        "JOIN sys_user u ON ur.user_id=u.id " +
                        "WHERE ur.tenant_id=? AND r.tenant_id=? AND r.role_code=? AND r.is_deleted=0 AND u.is_deleted=0 " +
                        "ORDER BY ur.id DESC LIMIT 1",
                Long.class,
                tenantId,
                tenantId,
                roleCode
        );
        return ids.isEmpty() ? null : ids.get(0);
    }

    private void sendTenantAdminMail(Long tenantId, CreateTenantAdminRequest request, String action, String setupLink) {
        NotificationChannelProvider provider = providerMap.get("EMAIL");
        if (provider == null) {
            throw new BusinessException(10003, "邮件通道不可用");
        }
        List<Map<String, Object>> tenants = jdbcTemplate.queryForList(
                "SELECT tenant_code, tenant_name FROM tenant WHERE id=? LIMIT 1",
                tenantId
        );
        String tenantCode = tenants.isEmpty() ? "" : String.valueOf(tenants.get(0).get("tenant_code"));
        String tenantName = tenants.isEmpty() ? "" : String.valueOf(tenants.get(0).get("tenant_name"));
        String loginPath = "/" + tenantCode + "/login";
        String actionCn = "UPDATED".equals(action) ? "修改管理员" : "新增管理员";
        String subject = renderTemplate(tenantAdminMailSubjectTemplate, tenantCode, tenantName, loginPath, request.getPhone().trim(), setupLink, action, actionCn);
        String content = renderTemplate(tenantAdminMailBodyTemplate, tenantCode, tenantName, loginPath, request.getPhone().trim(), setupLink, action, actionCn);
        String payload = "{\"subject\":\"" + jsonEscape(subject) + "\",\"content\":\"" + jsonEscape(content) + "\",\"action\":\"" + jsonEscape(action)
                + "\",\"tenantCode\":\"" + jsonEscape(tenantCode) + "\",\"tenantName\":\"" + jsonEscape(tenantName) + "\",\"loginPath\":\""
                + jsonEscape(loginPath) + "\",\"phone\":\"" + jsonEscape(request.getPhone().trim()) + "\",\"setupLink\":\"" + jsonEscape(setupLink) + "\"}";
        NotificationSendResult result = provider.send(request.getEmail().trim(), payload, null);
        if (result == null || !result.isAccepted()) {
            throw new BusinessException(10001, "管理员账号邮件发送失败");
        }
    }

    private String renderTemplate(String template, String tenantCode, String tenantName, String loginPath,
                                  String phone, String setupLink, String action, String actionCn) {
        String value = template == null ? "" : template;
        value = value.replace("{tenantCode}", tenantCode == null ? "" : tenantCode);
        value = value.replace("{tenantName}", tenantName == null ? "" : tenantName);
        value = value.replace("{loginPath}", loginPath == null ? "" : loginPath);
        value = value.replace("{phone}", phone == null ? "" : phone);
        value = value.replace("{setupLink}", setupLink == null ? "" : setupLink);
        value = value.replace("{action}", action == null ? "" : action);
        value = value.replace("{actionCn}", actionCn == null ? "" : actionCn);
        return value;
    }

    private String jsonEscape(String raw) {
        if (raw == null) {
            return "";
        }
        return raw
                .replace("\\", "\\\\")
                .replace("\"", "\\\"")
                .replace("\r", "")
                .replace("\n", "\\n");
    }

    private void assertExists(Long tenantId) {
        Integer count = jdbcTemplate.queryForObject("SELECT COUNT(1) FROM tenant WHERE id=? AND is_deleted=0", Integer.class, tenantId);
        if (count == null || count == 0) {
            throw new BusinessException(10003, "租户不存在");
        }
    }

    private TenantInfo findById(Long id) {
        List<TenantInfo> list = jdbcTemplate.query(
                "SELECT id, tenant_code, tenant_name, logo_url, status, DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s') AS created_at " +
                        "FROM tenant WHERE id=? AND is_deleted=0",
                TENANT_ROW_MAPPER,
                id
        );
        if (list.isEmpty()) {
            throw new BusinessException(10003, "租户不存在");
        }
        return list.get(0);
    }

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

    private String normalizeNullable(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }

    private String normalizeContentType(String contentType) {
        if (contentType == null || contentType.trim().isEmpty()) {
            return "application/octet-stream";
        }
        return contentType.trim();
    }

    private String loadTenantSwitchAccountKey(Long tenantId, Long userId) {
        List<String> keys = jdbcTemplate.queryForList(
                "SELECT tenant_switch_account_key FROM sys_user WHERE tenant_id=? AND id=? AND is_deleted=0 LIMIT 1",
                String.class,
                tenantId,
                userId
        );
        return keys.isEmpty() ? "" : String.valueOf(keys.get(0) == null ? "" : keys.get(0)).trim();
    }

    private String resolveTenantSwitchAccountKeyByPhone(String phone, Long excludeUserId) {
        String normalizedPhone = phone == null ? "" : phone.trim();
        if (normalizedPhone.isEmpty()) {
            return "acct_" + UUID.randomUUID().toString().replace("-", "").toUpperCase();
        }
        String sql =
                "SELECT tenant_switch_account_key FROM sys_user " +
                "WHERE phone=? AND is_deleted=0 AND tenant_switch_account_key IS NOT NULL AND tenant_switch_account_key<>'' " +
                (excludeUserId == null ? "" : "AND id<>? ") +
                "ORDER BY id ASC LIMIT 1";
        List<String> keys = excludeUserId == null
                ? jdbcTemplate.queryForList(sql, String.class, normalizedPhone)
                : jdbcTemplate.queryForList(sql, String.class, normalizedPhone, excludeUserId);
        if (!keys.isEmpty()) {
            String existing = String.valueOf(keys.get(0) == null ? "" : keys.get(0)).trim();
            if (!existing.isEmpty()) {
                return existing;
            }
        }
        return "acct_" + UUID.randomUUID().toString().replace("-", "").toUpperCase();
    }

    private String generateTemporaryPassword() {
        return "Tmp@" + UUID.randomUUID().toString().replace("-", "").substring(0, 8) + "aA1";
    }
}
