package com.xly.erp.module.usr.service.impl;

import com.xly.erp.common.config.StubSecurityProperties;
import com.xly.erp.common.config.TenantProperties;
import com.xly.erp.common.exception.BizException;
import com.xly.erp.common.security.JwtUtil;
import com.xly.erp.common.security.SecurityContextHelper;
import com.xly.erp.module.usr.dto.CreateUserDTO;
import com.xly.erp.module.usr.dto.LoginDTO;
import com.xly.erp.module.usr.dto.UpdateUserDTO;
import com.xly.erp.module.usr.entity.User;
import com.xly.erp.module.usr.security.LoginAttemptStore;
import com.xly.erp.module.usr.vo.LoginVO;
import com.xly.erp.module.usr.vo.PermissionCategoryVO;
import com.xly.erp.module.usr.vo.UserBriefVO;
import com.xly.erp.module.usr.vo.UserListVO;
import com.xly.erp.module.usr.entity.UserPermission;
import com.xly.erp.module.usr.mapper.PermissionCategoryMapper;
import com.xly.erp.module.usr.mapper.StaffMapper;
import com.xly.erp.module.usr.mapper.UserMapper;
import com.xly.erp.module.usr.mapper.UserPermissionMapper;
import com.xly.erp.module.usr.service.UserService;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeParseException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements UserService {

    static final Set<String> USER_TYPES = Set.of("普通用户", "超级管理员");
    static final Set<String> LANGUAGES = Set.of("zh", "en", "zh-TW");
    static final String DEFAULT_PASSWORD = "666666";

    static final int MAX_PAGE_SIZE = 100;
    static final String STRING_TYPE = "STRING";
    static final String BOOLEAN_TYPE = "BOOLEAN";
    static final String DATE_TYPE = "DATE";

    private static final Map<String, FieldDef> FIELD_MAP = Map.of(
            "用户名", new FieldDef("u.sUserName", STRING_TYPE),
            "员工名", new FieldDef("s.sStaffName", STRING_TYPE),
            "用户号", new FieldDef("u.sUserNo", STRING_TYPE),
            "部门", new FieldDef("s.sDepartment", STRING_TYPE),
            "用户类型", new FieldDef("u.sUserType", STRING_TYPE),
            "作废", new FieldDef("u.bDeleted", BOOLEAN_TYPE),
            "登录日期", new FieldDef("DATE(u.tLastLoginDate)", DATE_TYPE),
            "制单人", new FieldDef("u.sCreatedBy", STRING_TYPE)
    );

    private static final Map<String, String> MATCH_MAP = Map.of(
            "包含", "contains",
            "不包含", "notContains",
            "等于", "equals"
    );

    private record FieldDef(String column, String type) {}

    private final UserMapper userMapper;
    private final UserPermissionMapper userPermissionMapper;
    private final StaffMapper staffMapper;
    private final PermissionCategoryMapper permissionCategoryMapper;
    private final TenantProperties tenant;
    private final StubSecurityProperties stub;
    private final BCryptPasswordEncoder passwordEncoder;
    private final JwtUtil jwtUtil;
    private final LoginAttemptStore loginAttemptStore;

    public UserServiceImpl(UserMapper userMapper,
                           UserPermissionMapper userPermissionMapper,
                           StaffMapper staffMapper,
                           PermissionCategoryMapper permissionCategoryMapper,
                           TenantProperties tenant,
                           StubSecurityProperties stub,
                           BCryptPasswordEncoder passwordEncoder,
                           JwtUtil jwtUtil,
                           LoginAttemptStore loginAttemptStore) {
        this.userMapper = userMapper;
        this.userPermissionMapper = userPermissionMapper;
        this.staffMapper = staffMapper;
        this.permissionCategoryMapper = permissionCategoryMapper;
        this.tenant = tenant;
        this.stub = stub;
        this.passwordEncoder = passwordEncoder;
        this.jwtUtil = jwtUtil;
        this.loginAttemptStore = loginAttemptStore;
    }

    @Override
    public Map<String, Object> create(CreateUserDTO dto) {
        if (!USER_TYPES.contains(dto.getSUserType())) {
            throw new BizException(40001, "sUserType: 取值非法");
        }
        if (!LANGUAGES.contains(dto.getSLanguage())) {
            throw new BizException(40001, "sLanguage: 取值非法");
        }
        if (dto.getIStaffId() != null && !staffMapper.existsActiveById(dto.getIStaffId())) {
            throw new BizException(40022, "职员不存在或已删除");
        }
        List<Integer> ids = dto.getPermissionCategoryIds();
        if (ids != null && !ids.isEmpty()) {
            int found = permissionCategoryMapper.countActiveByIds(ids);
            if (found != ids.size()) {
                throw new BizException(40023, "权限分类含无效 id");
            }
        }

        User entity = new User();
        entity.setSBrandsId(tenant.getBrandsId());
        entity.setSSubsidiaryId(tenant.getSubsidiaryId());
        entity.setTCreateDate(LocalDateTime.now());
        entity.setSUserNo(dto.getSUserNo());
        entity.setSUserName(dto.getSUserName());
        entity.setIStaffId(dto.getIStaffId());
        entity.setSUserType(dto.getSUserType());
        entity.setSLanguage(dto.getSLanguage());
        entity.setBCanModifyDocs(dto.getBCanModifyDocs() != null ? dto.getBCanModifyDocs() : false);
        entity.setSPasswordHash(passwordEncoder.encode(DEFAULT_PASSWORD));
        String createdBy = SecurityContextHelper.currentUserNo();
        entity.setSCreatedBy(createdBy);
        entity.setBDeleted(false);

        try {
            userMapper.insert(entity);
        } catch (DuplicateKeyException e) {
            throw new BizException(40020, "用户号或用户名已存在");
        }

        if (ids != null && !ids.isEmpty()) {
            for (Integer cid : ids) {
                UserPermission rel = new UserPermission();
                rel.setSBrandsId(tenant.getBrandsId());
                rel.setSSubsidiaryId(tenant.getSubsidiaryId());
                rel.setTCreateDate(LocalDateTime.now());
                rel.setIUserId(entity.getIIncrement());
                rel.setICategoryId(cid);
                rel.setSCreatedBy(createdBy);
                userPermissionMapper.insert(rel);
            }
        }

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("iIncrement", entity.getIIncrement());
        result.put("sUserNo", entity.getSUserNo());
        return result;
    }

    @Override
    public Integer update(Integer id, UpdateUserDTO dto) {
        User original = userMapper.selectById(id);
        if (original == null || Boolean.TRUE.equals(original.getBDeleted())) {
            throw new BizException(40400, "用户不存在或已删除");
        }
        if (!USER_TYPES.contains(dto.getSUserType())) {
            throw new BizException(40001, "sUserType: 取值非法");
        }
        if (!LANGUAGES.contains(dto.getSLanguage())) {
            throw new BizException(40001, "sLanguage: 取值非法");
        }
        if (dto.getIStaffId() != null && !staffMapper.existsActiveById(dto.getIStaffId())) {
            throw new BizException(40022, "职员不存在或已删除");
        }
        List<Integer> ids = dto.getPermissionCategoryIds();
        if (ids != null && !ids.isEmpty()) {
            int found = permissionCategoryMapper.countActiveByIds(ids);
            if (found != ids.size()) {
                throw new BizException(40023, "权限分类含无效 id");
            }
        }

        User entity = new User();
        entity.setIIncrement(id);
        entity.setSUserNo(dto.getSUserNo());
        entity.setSUserName(dto.getSUserName());
        entity.setIStaffId(dto.getIStaffId());
        entity.setSUserType(dto.getSUserType());
        entity.setSLanguage(dto.getSLanguage());
        entity.setBCanModifyDocs(dto.getBCanModifyDocs() != null ? dto.getBCanModifyDocs() : false);
        try {
            userMapper.updateById(entity);
        } catch (DuplicateKeyException e) {
            throw new BizException(40020, "用户号或用户名已存在");
        }

        userPermissionMapper.deleteByUserId(id);
        if (ids != null && !ids.isEmpty()) {
            String createdBy = SecurityContextHelper.currentUserNo();
            LocalDateTime now = LocalDateTime.now();
            for (Integer cid : ids) {
                UserPermission rel = new UserPermission();
                rel.setSBrandsId(tenant.getBrandsId());
                rel.setSSubsidiaryId(tenant.getSubsidiaryId());
                rel.setTCreateDate(now);
                rel.setIUserId(id);
                rel.setICategoryId(cid);
                rel.setSCreatedBy(createdBy);
                userPermissionMapper.insert(rel);
            }
        }
        return id;
    }

    @Override
    @Transactional(readOnly = true)
    public Map<String, Object> list(String field, String match, String value, Integer pageNum, Integer pageSize) {
        int p = (pageNum == null || pageNum < 1) ? 1 : pageNum;
        int size = (pageSize == null) ? 20 : pageSize;
        if (size > MAX_PAGE_SIZE) {
            throw new BizException(40002, "pageSize 超过 100");
        }
        String f = (field == null || field.isBlank()) ? "用户名" : field.trim();
        String m = (match == null || match.isBlank()) ? "包含" : match.trim();
        String v = value == null ? "" : value.trim();

        FieldDef def = FIELD_MAP.get(f);
        if (def == null) {
            throw new BizException(40001, "field 取值非法");
        }
        String matchOp = MATCH_MAP.get(m);
        if (matchOp == null) {
            throw new BizException(40001, "match 取值非法");
        }
        if (!STRING_TYPE.equals(def.type()) && !"equals".equals(matchOp)) {
            throw new BizException(40001, "field/match 组合非法");
        }

        Object queryValue = v;
        if (!v.isEmpty()) {
            if (BOOLEAN_TYPE.equals(def.type())) {
                queryValue = parseBoolean(v);
            } else if (DATE_TYPE.equals(def.type())) {
                try {
                    queryValue = LocalDate.parse(v).toString();
                } catch (DateTimeParseException e) {
                    throw new BizException(40001, "登录日期值格式非法（应为 YYYY-MM-DD）");
                }
            }
        } else {
            queryValue = "";
        }

        int offset = (p - 1) * size;
        List<UserListVO> records = userMapper.pageWithFilter(def.column(), matchOp, queryValue, offset, size);
        long total = userMapper.countWithFilter(def.column(), matchOp, queryValue);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("records", records);
        result.put("total", total);
        result.put("pageNum", p);
        result.put("pageSize", size);
        return result;
    }

    @Override
    @Transactional(readOnly = true)
    public UserListVO detail(Integer id) {
        UserListVO vo = userMapper.selectDetailById(id);
        if (vo == null || Boolean.TRUE.equals(vo.getBDeleted())) {
            throw new BizException(40400, "用户不存在或已删除");
        }
        vo.setPermissionCategoryIds(userPermissionMapper.selectCategoryIdsByUserId(id));
        return vo;
    }

    @Override
    @Transactional(readOnly = true)
    public List<PermissionCategoryVO> listPermissionCategories() {
        return permissionCategoryMapper.listActive();
    }

    private Integer parseBoolean(String v) {
        return switch (v.toLowerCase()) {
            case "true", "1" -> 1;
            case "false", "0" -> 0;
            default -> -1;
        };
    }

    @Override
    public LoginVO login(LoginDTO dto) {
        String userName = dto.getSUserName();
        loginAttemptStore.isLocked(userName).ifPresent(seconds -> {
            throw new BizException(42301, "账号临时锁定，剩余 " + seconds + " 秒");
        });
        User user = userMapper.selectByUserName(userName);
        if (user == null) {
            throw new BizException(40101, "用户名或密码错误");
        }
        if (Boolean.TRUE.equals(user.getBDeleted())) {
            throw new BizException(40102, "账号已禁用");
        }
        if (!passwordEncoder.matches(dto.getPassword(), user.getSPasswordHash())) {
            int newCount = loginAttemptStore.recordFailure(userName);
            if (newCount >= LoginAttemptStore.MAX_ATTEMPTS) {
                long remaining = loginAttemptStore.isLocked(userName).orElse(0L);
                throw new BizException(42301, "账号临时锁定，剩余 " + remaining + " 秒");
            }
            throw new BizException(40101, "用户名或密码错误");
        }
        loginAttemptStore.clearFailures(userName);
        userMapper.updateLastLoginDate(user.getIIncrement(), LocalDateTime.now());

        UserBriefVO brief = new UserBriefVO();
        brief.setIIncrement(user.getIIncrement());
        brief.setSUserNo(user.getSUserNo());
        brief.setSUserName(user.getSUserName());
        brief.setSUserType(user.getSUserType());
        brief.setSLanguage(user.getSLanguage());

        LoginVO vo = new LoginVO();
        vo.setAccessToken(jwtUtil.sign(user.getSUserNo()));
        vo.setRefreshToken(jwtUtil.signRefresh(user.getSUserNo()));
        vo.setExpiresIn(JwtUtil.ACCESS_TTL.toSeconds());
        vo.setUser(brief);
        return vo;
    }
}
