package com.writeoff.module.system.service;

import com.writeoff.common.api.PageResult;
import com.writeoff.common.exception.BusinessException;
import com.writeoff.common.model.ImportResult;
import com.writeoff.common.util.ImportValidationUtils;
import com.writeoff.module.system.dto.AssignUserRoleRequest;
import com.writeoff.module.system.dto.BindRolePermissionsRequest;
import com.writeoff.module.system.dto.CreateRoleRequest;
import com.writeoff.module.system.dto.CreateUserRequest;
import com.writeoff.module.system.dto.ImportUserItemRequest;
import com.writeoff.module.system.dto.ResetPasswordRequest;
import com.writeoff.module.system.dto.UpdateProfilePreferencesRequest;
import com.writeoff.module.system.dto.UpdateRoleRequest;
import com.writeoff.module.system.model.PermissionInfo;
import com.writeoff.module.system.model.ProfilePreferencesInfo;
import com.writeoff.module.system.model.RoleInfo;
import com.writeoff.module.system.model.SystemUser;
import com.writeoff.security.PasswordCodecService;
import com.writeoff.security.PasswordPolicyService;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;

import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Service
public class PlatformIamService {
    private final JdbcTemplate jdbcTemplate;
    private final PasswordPolicyService passwordPolicyService;
    private final PasswordCodecService passwordCodecService;

    private static final RowMapper<SystemUser> USER_ROW_MAPPER = (rs, n) -> new SystemUser(
            rs.getLong("id"),
            rs.getString("user_name"),
            rs.getString("phone"),
            rs.getString("email"),
            rs.getString("status"),
            rs.getString("valid_from"),
            rs.getString("valid_to")
    );

    private static final RowMapper<RoleInfo> ROLE_ROW_MAPPER = (rs, n) -> new RoleInfo(
            rs.getLong("id"),
            rs.getString("role_code"),
            rs.getString("role_name"),
            rs.getString("status")
    );

    private static final RowMapper<PermissionInfo> PERMISSION_ROW_MAPPER = (rs, n) -> new PermissionInfo(
            rs.getLong("id"),
            rs.getString("permission_code"),
            rs.getString("permission_name"),
            rs.getString("module")
    );

    public PlatformIamService(JdbcTemplate jdbcTemplate, PasswordPolicyService passwordPolicyService, PasswordCodecService passwordCodecService) {
        this.jdbcTemplate = jdbcTemplate;
        this.passwordPolicyService = passwordPolicyService;
        this.passwordCodecService = passwordCodecService;
    }

    public PageResult<SystemUser> listUsers() {
        return listUsers(null);
    }

    public PageResult<SystemUser> listUsers(String keyword) {
        String normalizedKeyword = keyword == null ? "" : keyword.trim();
        StringBuilder sql = new StringBuilder(
                "SELECT id, user_name, phone, email, status, " +
                        "DATE_FORMAT(valid_from, '%Y-%m-%d %H:%i:%s') AS valid_from, " +
                        "DATE_FORMAT(valid_to, '%Y-%m-%d %H:%i:%s') AS valid_to " +
                        "FROM platform_user WHERE is_deleted=0"
        );
        List<Object> args = new java.util.ArrayList<Object>();
        if (!normalizedKeyword.isEmpty()) {
            sql.append(" AND (user_name LIKE ? OR phone LIKE ? OR email LIKE ?)");
            String like = "%" + normalizedKeyword + "%";
            args.add(like);
            args.add(like);
            args.add(like);
        }
        sql.append(" ORDER BY id DESC");
        List<SystemUser> list = jdbcTemplate.query(
                sql.toString(),
                USER_ROW_MAPPER,
                args.toArray()
        );
        return new PageResult<SystemUser>(list, list.size(), 1, 100);
    }

    public SystemUser createUser(CreateUserRequest request) {
        assertPhoneAvailable(request.getPhone(), null);
        ImportValidationUtils.validateOptionalEmail(request.getEmail());
        ImportValidationUtils.validateDateRange(request.getValidFrom(), request.getValidTo());
        if (request.getPassword() == null || request.getPassword().trim().isEmpty()) {
            throw new BusinessException(10001, "\u5bc6\u7801\u4e0d\u80fd\u4e3a\u7a7a");
        }
        passwordPolicyService.validate(request.getPassword());
        String passwordHash = passwordCodecService.encode(request.getPassword());
        jdbcTemplate.update(
                "INSERT INTO platform_user (user_name, phone, email, password_hash, status, valid_from, valid_to, is_deleted, created_by, updated_by) " +
                        "VALUES (?, ?, ?, ?, 'ENABLED', ?, ?, 0, 0, 0)",
                request.getUserName().trim(),
                request.getPhone().trim(),
                normalizeOptionalText(request.getEmail()),
                passwordHash,
                parseTimestampOrDefault(request.getValidFrom(), LocalDateTime.now()),
                parseTimestampOrDefault(request.getValidTo(), LocalDateTime.of(2099, 12, 31, 23, 59, 59))
        );
        Long id = jdbcTemplate.queryForObject("SELECT IFNULL(MAX(id), 0) FROM platform_user", Long.class);
        return getUserById(id == null ? 0L : id);
    }

    public SystemUser updateUser(Long id, CreateUserRequest request) {
        assertUserExists(id);
        assertPhoneAvailable(request.getPhone(), id);
        ImportValidationUtils.validateOptionalEmail(request.getEmail());
        ImportValidationUtils.validateDateRange(request.getValidFrom(), request.getValidTo());
        String normalizedEmail = normalizeOptionalText(request.getEmail());
        if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) {
            passwordPolicyService.validate(request.getPassword());
            jdbcTemplate.update(
                    "UPDATE platform_user SET user_name=?, phone=?, email=?, password_hash=?, valid_from=?, valid_to=?, updated_at=CURRENT_TIMESTAMP WHERE id=?",
                    request.getUserName().trim(),
                    request.getPhone().trim(),
                    normalizedEmail,
                    passwordCodecService.encode(request.getPassword()),
                    parseTimestampOrDefault(request.getValidFrom(), LocalDateTime.now()),
                    parseTimestampOrDefault(request.getValidTo(), LocalDateTime.of(2099, 12, 31, 23, 59, 59)),
                    id
            );
        } else {
            jdbcTemplate.update(
                    "UPDATE platform_user SET user_name=?, phone=?, email=?, valid_from=?, valid_to=?, updated_at=CURRENT_TIMESTAMP WHERE id=?",
                    request.getUserName().trim(),
                    request.getPhone().trim(),
                    normalizedEmail,
                    parseTimestampOrDefault(request.getValidFrom(), LocalDateTime.now()),
                    parseTimestampOrDefault(request.getValidTo(), LocalDateTime.of(2099, 12, 31, 23, 59, 59)),
                    id
            );
        }
        return getUserById(id);
    }

    public ImportResult importUsers(List<ImportUserItemRequest> users) {
        ImportResult result = new ImportResult();
        result.setTotal(users == null ? 0 : users.size());
        if (users == null) {
            return result;
        }
        Set<String> batchPhones = new HashSet<String>();
        for (int i = 0; i < users.size(); i++) {
            ImportUserItemRequest item = users.get(i);
            int rowNo = i + 2;
            try {
                Long roleId = validateImportUser(item, batchPhones);
                CreateUserRequest request = new CreateUserRequest();
                request.setUserName(item.getUserName() == null ? null : item.getUserName().trim());
                request.setPhone(item.getPhone() == null ? null : item.getPhone().trim());
                request.setPassword(item.getPassword() == null ? null : item.getPassword().trim());
                request.setEmail(item.getEmail() == null ? null : item.getEmail().trim());
                request.setValidFrom(item.getValidFrom());
                request.setValidTo(item.getValidTo());
                SystemUser created = createUser(request);
                if (roleId != null) {
                    assignRole(created.getId(), roleId);
                }
                result.markSuccess();
            } catch (Exception ex) {
                result.addError(rowNo, buildUserIdentifier(item), resolveImportMessage(ex));
            }
        }
        return result;
    }

    public void assignRole(AssignUserRoleRequest request) {
        assignRole(request.getUserId(), request.getRoleId());
    }

    public void assignRole(Long userId, Long roleId) {
        assertUserExists(userId);
        assertRoleExists(roleId);
        Integer exists = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM platform_user_role WHERE user_id=? AND role_id=?",
                Integer.class,
                userId,
                roleId
        );
        if (exists != null && exists > 0) {
            return;
        }
        Long nextId = jdbcTemplate.queryForObject("SELECT IFNULL(MAX(id), 0) + 1 FROM platform_user_role", Long.class);
        jdbcTemplate.update(
                "INSERT INTO platform_user_role (id, user_id, role_id) VALUES (?, ?, ?)",
                nextId == null ? 1L : nextId,
                userId,
                roleId
        );
    }

    public void enableUser(Long userId) {
        assertUserExists(userId);
        jdbcTemplate.update("UPDATE platform_user SET status='ENABLED', updated_at=CURRENT_TIMESTAMP WHERE id=?", userId);
    }

    public void disableUser(Long userId) {
        assertUserExists(userId);
        jdbcTemplate.update("UPDATE platform_user SET status='DISABLED', updated_at=CURRENT_TIMESTAMP WHERE id=?", userId);
    }

    public void resetPassword(Long userId, ResetPasswordRequest request) {
        assertUserExists(userId);
        passwordPolicyService.validate(request.getNewPassword());
        jdbcTemplate.update(
                "UPDATE platform_user SET password_hash=?, updated_at=CURRENT_TIMESTAMP WHERE id=?",
                passwordCodecService.encode(request.getNewPassword()),
                userId
        );
    }

    public void changeMyPassword(Long userId, String oldPassword, String newPassword) {
        assertUserExists(userId);
        passwordPolicyService.validate(newPassword);
        String currentPasswordHash = jdbcTemplate.queryForObject(
                "SELECT password_hash FROM platform_user WHERE id=? AND is_deleted=0 LIMIT 1",
                String.class,
                userId
        );
        if (!passwordCodecService.matches(oldPassword, currentPasswordHash)) {
            throw new BusinessException(11001, "鍘熷瘑鐮佷笉姝ｇ‘");
        }
        jdbcTemplate.update(
                "UPDATE platform_user SET password_hash=?, updated_at=CURRENT_TIMESTAMP WHERE id=?",
                passwordCodecService.encode(newPassword),
                userId
        );
    }

    public void onSuccessfulLogin(Long userId, String rawPassword) {
        List<String> hashes = jdbcTemplate.queryForList(
                "SELECT password_hash FROM platform_user WHERE id=? AND is_deleted=0 LIMIT 1",
                String.class,
                userId
        );
        if (hashes.isEmpty()) {
            return;
        }
        String currentHash = hashes.get(0);
        if (passwordCodecService.isEncoded(currentHash) || !passwordCodecService.matches(rawPassword, currentHash)) {
            return;
        }
        jdbcTemplate.update(
                "UPDATE platform_user SET password_hash=?, updated_at=CURRENT_TIMESTAMP WHERE id=?",
                passwordCodecService.encode(rawPassword),
                userId
        );
    }

    public ProfilePreferencesInfo getMyPreferences(Long userId) {
        assertUserExists(userId);
        List<Map<String, Object>> rows = jdbcTemplate.queryForList(
                "SELECT ui_theme_mode, ui_density, ui_theme_scheme FROM platform_user WHERE id=? AND is_deleted=0 LIMIT 1",
                userId
        );
        if (rows.isEmpty()) {
            throw new BusinessException(10003, "用户不存在");
        }
        return new ProfilePreferencesInfo(
                normalizeThemeMode(rows.get(0).get("ui_theme_mode")),
                normalizeDensity(rows.get(0).get("ui_density")),
                normalizeThemeScheme(rows.get(0).get("ui_theme_scheme"))
        );
    }

    public ProfilePreferencesInfo updateMyPreferences(Long userId, UpdateProfilePreferencesRequest request) {
        assertUserExists(userId);
        String themeMode = normalizeThemeMode(request == null ? null : request.getThemeMode());
        String density = normalizeDensity(request == null ? null : request.getDensity());
        String themeScheme = normalizeThemeScheme(request == null ? null : request.getThemeScheme());
        jdbcTemplate.update(
                "UPDATE platform_user SET ui_theme_mode=?, ui_density=?, ui_theme_scheme=?, updated_at=CURRENT_TIMESTAMP WHERE id=?",
                themeMode,
                density,
                themeScheme,
                userId
        );
        return new ProfilePreferencesInfo(themeMode, density, themeScheme);
    }

    public PageResult<RoleInfo> listRoles() {
        List<RoleInfo> list = jdbcTemplate.query(
                "SELECT id, role_code, role_name, status FROM platform_role WHERE is_deleted=0 ORDER BY id DESC",
                ROLE_ROW_MAPPER
        );
        return new PageResult<RoleInfo>(list, list.size(), 1, 100);
    }

    public RoleInfo createRole(CreateRoleRequest request) {
        Integer exists = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM platform_role WHERE role_code=?",
                Integer.class,
                request.getRoleCode().trim()
        );
        if (exists != null && exists > 0) {
            throw new BusinessException(10001, "\u89d2\u8272\u7f16\u7801\u5df2\u5b58\u5728");
        }
        Long nextId = jdbcTemplate.queryForObject("SELECT IFNULL(MAX(id), 0) + 1 FROM platform_role", Long.class);
        long roleId = nextId == null ? 1L : nextId;
        jdbcTemplate.update(
                "INSERT INTO platform_role (id, role_code, role_name, status, is_deleted, created_by, updated_by) VALUES (?, ?, ?, 'ENABLED', 0, 0, 0)",
                roleId,
                request.getRoleCode().trim(),
                request.getRoleName().trim()
        );
        return new RoleInfo(roleId, request.getRoleCode().trim(), request.getRoleName().trim(), "ENABLED");
    }

    public RoleInfo updateRole(Long roleId, UpdateRoleRequest request) {
        assertRoleExists(roleId);
        jdbcTemplate.update(
                "UPDATE platform_role SET role_name=?, updated_at=CURRENT_TIMESTAMP WHERE id=?",
                request.getRoleName().trim(),
                roleId
        );
        return jdbcTemplate.queryForObject(
                "SELECT id, role_code, role_name, status FROM platform_role WHERE id=?",
                ROLE_ROW_MAPPER,
                roleId
        );
    }

    public void enableRole(Long roleId) {
        updateRoleStatus(roleId, "ENABLED");
    }

    public void disableRole(Long roleId) {
        updateRoleStatus(roleId, "DISABLED");
    }

    public PageResult<PermissionInfo> listPermissions() {
        List<PermissionInfo> list = jdbcTemplate.query(
                "SELECT id, permission_code, permission_name, module FROM platform_permission ORDER BY module ASC, id ASC",
                PERMISSION_ROW_MAPPER
        );
        return new PageResult<PermissionInfo>(list, list.size(), 1, 200);
    }

    public List<Long> getRolePermissionIds(Long roleId) {
        assertRoleExists(roleId);
        return jdbcTemplate.queryForList(
                "SELECT permission_id FROM platform_role_permission WHERE role_id=? ORDER BY permission_id ASC",
                Long.class,
                roleId
        );
    }

    public void bindRolePermissions(Long roleId, BindRolePermissionsRequest request) {
        assertRoleExists(roleId);
        jdbcTemplate.update("DELETE FROM platform_role_permission WHERE role_id=?", roleId);
        if (request.getPermissionIds() == null || request.getPermissionIds().isEmpty()) {
            return;
        }
        Long nextId = jdbcTemplate.queryForObject("SELECT IFNULL(MAX(id), 0) + 1 FROM platform_role_permission", Long.class);
        long currentId = nextId == null ? 1L : nextId;
        for (Long permissionId : request.getPermissionIds()) {
            Integer permExists = jdbcTemplate.queryForObject(
                    "SELECT COUNT(1) FROM platform_permission WHERE id=?",
                    Integer.class,
                    permissionId
            );
            if (permExists == null || permExists == 0) {
                throw new BusinessException(10003, "\u6743\u9650\u4e0d\u5b58\u5728");
            }
            jdbcTemplate.update(
                    "INSERT INTO platform_role_permission (id, role_id, permission_id) VALUES (?, ?, ?)",
                    currentId++,
                    roleId,
                    permissionId
            );
        }
    }

    private SystemUser getUserById(Long userId) {
        List<SystemUser> list = jdbcTemplate.query(
                "SELECT id, user_name, phone, email, status, " +
                        "DATE_FORMAT(valid_from, '%Y-%m-%d %H:%i:%s') AS valid_from, " +
                        "DATE_FORMAT(valid_to, '%Y-%m-%d %H:%i:%s') AS valid_to " +
                        "FROM platform_user WHERE id=? AND is_deleted=0",
                USER_ROW_MAPPER,
                userId
        );
        if (list.isEmpty()) {
            throw new BusinessException(10003, "\u7528\u6237\u4e0d\u5b58\u5728");
        }
        return list.get(0);
    }

    private void assertUserExists(Long userId) {
        Integer count = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM platform_user WHERE id=? AND is_deleted=0",
                Integer.class,
                userId
        );
        if (count == null || count == 0) {
            throw new BusinessException(10003, "\u7528\u6237\u4e0d\u5b58\u5728");
        }
    }

    private void assertRoleExists(Long roleId) {
        Integer count = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM platform_role WHERE id=? AND is_deleted=0",
                Integer.class,
                roleId
        );
        if (count == null || count == 0) {
            throw new BusinessException(10003, "\u89d2\u8272\u4e0d\u5b58\u5728");
        }
    }

    private void updateRoleStatus(Long roleId, String status) {
        assertRoleExists(roleId);
        jdbcTemplate.update(
                "UPDATE platform_role SET status=?, updated_at=CURRENT_TIMESTAMP WHERE id=?",
                status,
                roleId
        );
    }

    private void assertPhoneAvailable(String phone, Long excludeUserId) {
        ImportValidationUtils.validatePhone(phone);
        String normalizedPhone = phone.trim();
        Integer count;
        if (excludeUserId == null) {
            count = jdbcTemplate.queryForObject(
                    "SELECT COUNT(1) FROM platform_user WHERE phone=? AND is_deleted=0",
                    Integer.class,
                    normalizedPhone
            );
        } else {
            count = jdbcTemplate.queryForObject(
                    "SELECT COUNT(1) FROM platform_user WHERE phone=? AND is_deleted=0 AND id<>?",
                    Integer.class,
                    normalizedPhone,
                    excludeUserId
            );
        }
        if (count != null && count > 0) {
            throw new BusinessException(10001, "\u624b\u673a\u53f7\u5df2\u5b58\u5728");
        }
    }

    private Long validateImportUser(ImportUserItemRequest item, Set<String> batchPhones) {
        if (item == null) {
            throw new BusinessException(10001, "\u5bfc\u5165\u884c\u4e0d\u80fd\u4e3a\u7a7a");
        }
        if (item.getUserName() == null || item.getUserName().trim().isEmpty()) {
            throw new BusinessException(10001, "\u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a");
        }
        if (item.getPassword() == null || item.getPassword().trim().isEmpty()) {
            throw new BusinessException(10001, "\u5bc6\u7801\u4e0d\u80fd\u4e3a\u7a7a");
        }
        ImportValidationUtils.validatePhone(item.getPhone());
        ImportValidationUtils.validateOptionalEmail(item.getEmail());
        ImportValidationUtils.validateDateRange(item.getValidFrom(), item.getValidTo());
        passwordPolicyService.validate(item.getPassword().trim());
        String phone = ImportValidationUtils.trim(item.getPhone());
        if (!batchPhones.add(phone)) {
            throw new BusinessException(10001, "\u6279\u6b21\u5185\u624b\u673a\u53f7\u91cd\u590d");
        }
        String roleCode = ImportValidationUtils.trim(item.getRoleCode());
        if (roleCode.isEmpty()) {
            return null;
        }
        Long roleId = findRoleIdByCode(roleCode);
        if (roleId == null) {
            throw new BusinessException(10001, "\u89d2\u8272\u7f16\u7801\u4e0d\u5b58\u5728: " + roleCode);
        }
        return roleId;
    }

    private Long findRoleIdByCode(String roleCode) {
        List<Long> ids = jdbcTemplate.queryForList(
                "SELECT id FROM platform_role WHERE role_code=? AND is_deleted=0 LIMIT 1",
                Long.class,
                roleCode
        );
        return ids.isEmpty() ? null : ids.get(0);
    }

    private String buildUserIdentifier(ImportUserItemRequest item) {
        if (item == null) {
            return "";
        }
        String userName = item.getUserName() == null ? "" : item.getUserName().trim();
        String phone = item.getPhone() == null ? "" : item.getPhone().trim();
        if (!userName.isEmpty() && !phone.isEmpty()) {
            return userName + "/" + phone;
        }
        return !userName.isEmpty() ? userName : phone;
    }

    private String resolveImportMessage(Exception ex) {
        if (ex instanceof BusinessException) {
            return ex.getMessage();
        }
        return ex.getMessage() == null || ex.getMessage().trim().isEmpty()
                ? "\u5bfc\u5165\u5931\u8d25"
                : ex.getMessage();
    }

    private String normalizeOptionalText(String raw) {
        String value = ImportValidationUtils.trim(raw);
        return value.isEmpty() ? null : value;
    }

    private Timestamp parseTimestampOrDefault(String raw, LocalDateTime defaultValue) {
        if (raw == null || raw.trim().isEmpty()) {
            return Timestamp.valueOf(defaultValue);
        }
        String normalized = normalizeDateTimeString(raw);
        return Timestamp.valueOf(LocalDateTime.parse(normalized, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    }

    private String normalizeDateTimeString(String raw) {
        String normalized = raw.trim().replace("T", " ");
        if (normalized.length() == 16) {
            return normalized + ":00";
        }
        return normalized;
    }

    public String normalizeThemeMode(Object raw) {
        String value = raw == null ? "" : String.valueOf(raw).trim().toUpperCase();
        if ("LIGHT".equals(value) || "DARK".equals(value) || "SYSTEM".equals(value)) {
            return value;
        }
        return "SYSTEM";
    }

    public String normalizeDensity(Object raw) {
        String value = raw == null ? "" : String.valueOf(raw).trim().toUpperCase();
        if ("COMPACT".equals(value) || "COMFORTABLE".equals(value)) {
            return value;
        }
        return "COMFORTABLE";
    }

    public String normalizeThemeScheme(Object raw) {
        String value = raw == null ? "" : String.valueOf(raw).trim().toUpperCase();
        if (
                "SLATE".equals(value) ||
                "OCEAN".equals(value) ||
                "FOREST".equals(value) ||
                "GRAPHITE".equals(value) ||
                "AMBER".equals(value) ||
                "RUBY".equals(value) ||
                "MIST".equals(value) ||
                "SAGE".equals(value) ||
                "DAWN".equals(value)
        ) {
            return value;
        }
        return "SLATE";
    }
}
