package com.neusoft.smart.pos.user.service;

import com.google.common.base.Strings;
import com.neusoft.smart.pos.dto.AlterUserRequest;
import com.neusoft.smart.pos.dto.device.request.DeviceQueryRequest;
import com.neusoft.smart.pos.dto.device.response.DeviceResponse;
import com.neusoft.smart.pos.dto.member.request.CreateMemberRequest;
import com.neusoft.smart.pos.dto.member.request.GetMembersRequest;
import com.neusoft.smart.pos.dto.org.Shop;
import com.neusoft.smart.pos.dto.org.response.PostOrgGroupResponse;
import com.neusoft.smart.pos.dto.org.response.PostShopResponse;
import com.neusoft.smart.pos.dto.user.*;
import com.neusoft.smart.pos.framework.bean.PaginationExtendBean;
import com.neusoft.smart.pos.framework.constants.RoleConstant;
import com.neusoft.smart.pos.framework.dto.ResponseData;
import com.neusoft.smart.pos.framework.exception.BusinessCommonException;
import com.neusoft.smart.pos.framework.utils.BeanUtil;
import com.neusoft.smart.pos.redis.bean.DeviceInfo;
import com.neusoft.smart.pos.redis.bean.UserInfo;
import com.neusoft.smart.pos.user.constants.SecurityConstant;
import com.neusoft.smart.pos.user.persistent.domain.*;
import com.neusoft.smart.pos.user.persistent.domain.Permission;
import com.neusoft.smart.pos.user.persistent.domain.User;
import com.neusoft.smart.pos.user.persistent.mapper.UserMapper;
import com.neusoft.smart.pos.user.persistent.mapper.UserShopMapper;
import com.neusoft.smart.pos.utils.PosDeviceRedisUtils;
import com.neusoft.smart.pos.utils.RequestHeaderUtil;
import com.neusoft.smart.pos.utils.UserCacheUtils;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.val;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import sun.misc.BASE64Decoder;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

import static com.neusoft.smart.pos.framework.constants.ApplicationConstants.SUCCESS_CODE;
import static com.neusoft.smart.pos.framework.constants.RoleConstant.*;
import static com.neusoft.smart.pos.framework.utils.IdGenerateUtils.randomLetterNumber;

@Service
public class UserService {

    @Resource
    private PermissionService permissionService;

    @Resource
    private LoginRecordService loginRecordService;

    @Resource
    private RemoteOrgService orgService;

    @Resource
    private RemoteDeviceService remoteDeviceService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Resource
    private UserMapper userMapper;

    @Resource
    private UserShopMapper userShopMapper;

    @Resource
    private RemoteOrgGroupService orgGroupService;

    @Resource
    private RemoteMemberService memberService;

    private boolean greaterThan(User user) {
        switch (RequestHeaderUtil.getRole()) {
            case "sa":
                return true;
            case "admin":
                Integer orgId = RequestHeaderUtil.getOrgId();
                return user.getOrg().equals(orgId);
            case "user":
                final List<Integer> storeIds = RequestHeaderUtil.getStoreIds();
                final Set<Integer> storeIds1 = getStoreIds(user.getUsername());
                return storeIds1 != null && storeIds1.parallelStream().anyMatch(integer -> storeIds != null && storeIds.contains(integer));
            default:
                return false;
        }
    }

    private Set<Integer> getStoreIds(String username) {
        final User user = selectByUsername(username);
        if (user == null) return Collections.emptySet();
        UserShopExample example = new UserShopExample();
        final UserShopExample.Criteria criteria = example.createCriteria();
        criteria.andUsrEqualTo(user.getId());
        final List<UserShopKey> userShopKeys = userShopMapper.selectByExample(example);
        return userShopKeys.parallelStream().map(UserShopKey::getShop).collect(Collectors.toSet());
    }

    public User selectByUsername(String username) {
        UserExample example = new UserExample();
        example.createCriteria().andUsernameEqualTo(username);
        List<User> users = userMapper.selectByExample(example);
        if (users.size() > 0) {
            return users.get(0);
        }
        return null;
    }

    public void createUser(User user) {
        if ((ROLE_ADMIN.equals(user.getRole()) || ROLE_USER.equals(user.getRole())) && null == user.getOrg()) {
            throw new BusinessCommonException("500", "创建集团用户和门店用户时集团id不可为空");
        }
        User oldUser = selectByUsername(user.getUsername());
        if (oldUser != null) {
            throw new BusinessCommonException("500", "用户名已存在");
        }
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        userMapper.insertSelective(user);
        loadUserData2Redis(user);
    }

    public int changePassword(String username, String newPassword) {
        User user = selectByUsername(username);
        if (user != null) {
            if (!greaterThan(user)) {
                throw new BusinessCommonException("500", "无权进行此操作");
            }
            user.setPassword(passwordEncoder.encode(newPassword));
            return userMapper.updateByPrimaryKeySelective(user);
        }
        return 0;
    }

    private void posLogin(User user, String sn) {
        if (user.getRole().equals(ROLE_SA)) {
            throw new BusinessCommonException("500", "不可直接用超管账户登录");
        }
        if (user.getRole().equals(ROLE_ADMIN)) {
            throw new BusinessCommonException("500", "请将设备绑定到门店后再进行登录");
        }
        DeviceQueryRequest deviceQueryRequest = new DeviceQueryRequest();
        deviceQueryRequest.setSerialNo(sn);
        ResponseData<PaginationExtendBean<DeviceResponse>> paginationExtendBeanResponseData = remoteDeviceService.fetchList(deviceQueryRequest);
        String code = paginationExtendBeanResponseData.getCode();
        if (!StringUtils.equals(code, SUCCESS_CODE)) {
            throw new BusinessCommonException(code, paginationExtendBeanResponseData.getMessage());
        }
        PaginationExtendBean<DeviceResponse> result = paginationExtendBeanResponseData.getResult();
        boolean login = result != null && result.getTotal() > 0 && result.getRows().parallelStream().anyMatch(deviceResponse -> {
            UserShopExample example = new UserShopExample();
            UserShopExample.Criteria criteria = example.createCriteria();
            criteria.andShopEqualTo(deviceResponse.getStoreId());
            criteria.andUsrEqualTo(user.getId());
            return userShopMapper.countByExample(example) > 0;
        });
        if (!login) throw new BusinessCommonException("500", "未找到该用户");
    }

    public LoginResponse login(String username, String password, String captcha, String captchaId, String sn) {
        LoginResponse loginResponse = new LoginResponse();
        User user = selectByUsername(username);

        if (user == null) {
            throw new BusinessCommonException("500", "未找到该用户");
        }
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new BusinessCommonException("500", "错误的用户名或密码");
        }

        if (StringUtils.isNotEmpty(sn) && StringUtils.isEmpty(captcha)) {
            posLogin(user, sn);
        } else if (!StringUtils.equalsIgnoreCase(captchaId, captcha)) {
            throw new BusinessCommonException("500", "验证码错误");
        }

        String jwt = generateJwtToken(user.getUsername(), user.getRole());
        Set<String> permissions;
        if (!user.getRole().equals("ca")) {
            permissions = permissionService.selectPersissionsByUser(user).stream()
                    .map(Permission::getUrl)
                    .collect(Collectors.toSet());
        } else {
            permissions = Collections.emptySet();
        }
        loginResponse.setToken(jwt);
        Set<String> permissionGroups;
        if (user.getRole().equals("ca")) {
            permissionGroups = Collections.emptySet();
        } else {
            permissionGroups = permissionService.selectPermissionGroupsByUser(user).stream().map(PermissionGroup::getName).collect(Collectors.toSet());
        }
        loginResponse.setOrgId(user.getOrg());
        loginResponse.setName(user.getUsername());
        // loginResponse.setAvatar(user.getAvatar());
        if (StringUtils.isNotEmpty(sn)) {
            final DeviceInfo deviceInfoBySerialNo = PosDeviceRedisUtils.getDeviceInfoBySerialNo(sn);
            final Integer storeId = deviceInfoBySerialNo.getStoreId();
            loginResponse.setShopIds(Collections.singleton(storeId));
            final ResponseData<PostShopResponse> shopDetail = orgService.getShopDetail(storeId);
            final PostShopResponse shopDetailResult = shopDetail.getResult();
            loginResponse.setPosShopName(shopDetailResult.getName());
            loginResponse.setPosShopProvince(shopDetailResult.getProvince());
            loginResponse.setPosShopCity(shopDetailResult.getCity());
            loginResponse.setPosShopAddress(shopDetailResult.getAddress());
            loginResponse.setPosShopPhone(shopDetailResult.getPhone());
            loginResponse.setPosShopSettle(shopDetailResult.getSettle());
            // record login
            loginRecordService.addUserLoginRecord(username, sn, user.getOrg(), storeId);
        } else {
            loginResponse.setShopIds(orgService.getMyShops(username).getResult().parallelStream().map(Shop::getId).collect(Collectors.toSet()));
        }
        loginResponse.setType(user.getRole());
        loginResponse.setPermissionGroups(permissionGroups);
        loginResponse.setPermissions(permissions);
        loginResponse.setTimestamp(Calendar.getInstance().getTimeInMillis());
        return loginResponse;
    }

    public LoginThirdPartResponse loginThirdPart(LoginThirdPartInfo info) {
        LoginThirdPartResponse response = new LoginThirdPartResponse();
        //不检查参数空，应该在Controller中已经检查完成
        Integer channelId = decodeChannel(info.getChannel());
        Integer orgId = getOrgByOrgGroupId(channelId);

        val getMemberResponse = memberService.getMemberList(orgId, info.getOpenId());
        val getMemberData = getMemberResponse.getResult().getData();
        User user = null;
        if(getMemberData == null || getMemberData.size() == 0) {
            user = createUserInner(info.getNickname(), orgId);

            val addMemberRequest = new CreateMemberRequest();
            addMemberRequest.setUserId(user.getId());
            addMemberRequest.setName(user.getUsername());
            addMemberRequest.setOpenId(info.getOpenId());
            addMemberRequest.setOrg(user.getOrg());
            addMemberRequest.setMemberType(3);

            val addMemberResponse = memberService.addMember(addMemberRequest);

            if(!"200".equals(addMemberResponse.getCode())) {
                throw new BusinessCommonException("500", "创建用户失败: " + addMemberResponse.getMessage());
            }
        } else {
            val memberData = getMemberData.get(0);
            user = userMapper.selectByPrimaryKey(memberData.getUserId());
        }

        if(user == null) {
            throw new BusinessCommonException("500", "未找到该用户");
        }

        //FIXME:貌似不需要记录登录信息或者不应该使用这个接口记录登录信息
        loginRecordService.addUserLoginRecord(user.getUsername(), "", user.getOrg(), 0);

        response.setUserId(user.getId().toString());
        response.setUserName(user.getUsername());
        response.setToken(generateJwtToken(user.getUsername(), ROLE_ENDUSER));

        return response;
    }

    public LogoutResponse logout(String username, String sn, String isShift) {
        LogoutResponse logoutResponse = new LogoutResponse();
        if(StringUtils.isEmpty(sn)) {
            throw new BusinessCommonException("500", "设备SN不能为空");
        }
        User user = selectByUsername(username);
        if (user == null) {
            throw new BusinessCommonException("500", "未找到该用户");
        }
        final DeviceInfo deviceInfoBySerialNo = PosDeviceRedisUtils.getDeviceInfoBySerialNo(sn);
        final Integer storeId = deviceInfoBySerialNo.getStoreId();
        // record logout
        loginRecordService.addUserLogoutRecord(username,sn,user.getOrg(),storeId,isShift);
        logoutResponse.setReturnCode("SUCCESS");
        logoutResponse.setTimestamp(Calendar.getInstance().getTimeInMillis());
        return logoutResponse;
    }

    public QueryLastLoginTimeResponse queryLastLoginTime(String sn) {
        QueryLastLoginTimeResponse queryLastLoginTimeResponse = new QueryLastLoginTimeResponse();
        if(StringUtils.isEmpty(sn)) {
            throw new BusinessCommonException("500", "设备SN不能为空");
        }
        UserLoginRecordKey record = loginRecordService.selectLoginTimeAfterLastShifted(sn);
        if(record != null) {
            queryLastLoginTimeResponse.setSn(sn);
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            queryLastLoginTimeResponse.setTime(formatter.format(record.getCreatedAt()));
            queryLastLoginTimeResponse.setUsername(record.getUsername());
        } else {
            System.out.println("there is no login record after last shift");
        }
        return queryLastLoginTimeResponse;
    }

    public UserInfoResponse userinfo(String username) {
        User user = selectByUsername(username);
        UserInfoResponse userInfoResponse = new UserInfoResponse();
        userInfoResponse.setOrgId(user.getOrg());
        userInfoResponse.setName(user.getUsername());
//         userInfoResponse.setAvatar(user.getAvatar());
        userInfoResponse.setShopIds(orgService.getMyShops(username).getResult().parallelStream().map(Shop::getId).collect(Collectors.toSet()));
        userInfoResponse.setType(user.getRole());
        userInfoResponse.setPermissionGroups(selectPersissionGroupssByUsername(username).parallelStream().map(PermissionGroup::getName).collect(Collectors.toSet()));
        loadUserData2Redis(user);
        return userInfoResponse;
    }

    public List<Permission> selectPersissionsByUsername(String username) {
        User user = selectByUsername(username);
        return permissionService.selectPersissionsByUser(user);
    }

    public List<PermissionGroup> selectPersissionGroupssByUsername(String username) {
        User user = selectByUsername(username);
        return permissionService.selectPermissionGroupsByUser(user);
    }


    public void loadUserData2Redis() {
        List<User> users = userMapper.selectByExample(new UserExample());
        users.parallelStream().forEach(this::loadUserData2Redis);
    }

    private void loadUserData2Redis(User user) {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(user.getId());
        userInfo.setRole(user.getRole());
        userInfo.setOrgId(user.getOrg());
        userInfo.setUserName(user.getUsername());
        switch (user.getRole()) {
            case ROLE_ADMIN:
                userInfo.setStoreIds(orgService.queryShopListByOrgId(user.getOrg(), 0).getResult()
                        .parallelStream()
                        .map(PostShopResponse::getId)
                        .collect(Collectors.toList()));
                break;
            case ROLE_USER:
                UserShopExample userShopExample = new UserShopExample();
                UserShopExample.Criteria criteria = userShopExample.createCriteria();
                criteria.andUsrEqualTo(user.getId());
                List<Integer> shopIds = userShopMapper.selectByExample(userShopExample).parallelStream().map(UserShopKey::getShop).collect(Collectors.toList());
                userInfo.setStoreIds(shopIds);
                break;
        }
        userInfo.setPermissions(BeanUtil.parseList(this.selectPersissionsByUsername(user.getUsername()), com.neusoft.smart.pos.dto.user.Permission.class));
        UserCacheUtils.updateUserInfo(userInfo);
    }

    public void loadUserData2RedisByShop(Integer shopId) {
        UserShopExample userShopExample = new UserShopExample();
        UserShopExample.Criteria criteria = userShopExample.createCriteria();
        criteria.andShopEqualTo(shopId);
        userShopMapper.selectByExample(userShopExample).parallelStream()
                .map(UserShopKey::getUsr)
                .map(userMapper::selectByPrimaryKey)
                .forEach(this::loadUserData2Redis);
    }

    public void loadUserData2RedisByOrg(Integer orgId) {
        UserExample userExample = new UserExample();
        UserExample.Criteria criteria = userExample.createCriteria();
        criteria.andRoleEqualTo(ROLE_ADMIN);
        criteria.andOrgEqualTo(orgId);
        userMapper.selectByExample(userExample).parallelStream()
                .forEach(this::loadUserData2Redis);
    }

    public List<User> list(UserQuery query) {
        UserExample example = new UserExample();
        example.setOrderByClause("id desc");
        if (query.getOffset() != null) example.setOffset(query.getOffset());
        if (query.getLimit() != null) example.setLimit(query.getLimit());
        UserExample.Criteria baseCriteria = example.createCriteria();
        baseCriteria.andIdEqualTo(RequestHeaderUtil.getUserId());
        UserExample.Criteria mainCriteria = example.or();
        if (!Strings.isNullOrEmpty(query.getUsername())) {
            baseCriteria.andUsernameLike("%" + query.getUsername() + "%");
            mainCriteria.andUsernameLike("%" + query.getUsername() + "%");
        }
        if (!Strings.isNullOrEmpty(query.getNickname())) {
            baseCriteria.andNicknameLike("%" + query.getNickname() + "%");
            mainCriteria.andNicknameLike("%" + query.getNickname() + "%");
        }
        if (!Strings.isNullOrEmpty(query.getRole())) {
            baseCriteria.andRoleEqualTo(query.getRole());
            mainCriteria.andRoleEqualTo(query.getRole());
        }
        if (query.getOrg() != null) {
            baseCriteria.andOrgEqualTo(query.getOrg());
            mainCriteria.andOrgEqualTo(query.getOrg());
        }
        final String role = RequestHeaderUtil.getRole();
        if (role.equals(ROLE_USER)) {
            final UserShopExample userShopExample = new UserShopExample();
            userShopExample.createCriteria().andShopIn(RequestHeaderUtil.getStoreIds());
            final List<Integer> ids = userShopMapper.selectByExample(userShopExample).parallelStream().map(UserShopKey::getUsr).collect(Collectors.toList());
            mainCriteria.andIdIn(ids).andRoleEqualTo(ROLE_CA);
        }
        if (role.equals(ROLE_ADMIN)) {
            mainCriteria.andRoleIn(Arrays.asList(ROLE_USER, ROLE_CA));
        }
        if (role.equals(ROLE_SA)) {
            mainCriteria.andRoleIn(Arrays.asList(ROLE_SA, ROLE_ADMIN, ROLE_USER, ROLE_CA));
        }
        return userMapper.selectByExample(example);
    }

    public int count(UserQuery query) {
        UserExample example = new UserExample();
        UserExample.Criteria baseCriteria = example.createCriteria();
        baseCriteria.andIdEqualTo(RequestHeaderUtil.getUserId());
        UserExample.Criteria mainCriteria = example.or();
        if (!Strings.isNullOrEmpty(query.getUsername())) {
            baseCriteria.andUsernameLike("%" + query.getUsername() + "%");
            mainCriteria.andUsernameLike("%" + query.getUsername() + "%");
        }
        if (!Strings.isNullOrEmpty(query.getNickname())) {
            baseCriteria.andNicknameLike("%" + query.getNickname() + "%");
            mainCriteria.andNicknameLike("%" + query.getNickname() + "%");
        }
        if (!Strings.isNullOrEmpty(query.getRole())) {
            baseCriteria.andRoleEqualTo(query.getRole());
            mainCriteria.andRoleEqualTo(query.getRole());
        }
        if (query.getOrg() != null) {
            baseCriteria.andOrgEqualTo(query.getOrg());
            mainCriteria.andOrgEqualTo(query.getOrg());
        }
        final String role = RequestHeaderUtil.getRole();
        if (role.equals(ROLE_USER)) {
            final UserShopExample userShopExample = new UserShopExample();
            userShopExample.createCriteria().andShopIn(RequestHeaderUtil.getStoreIds());
            final List<Integer> ids = userShopMapper.selectByExample(userShopExample).parallelStream().map(UserShopKey::getUsr).collect(Collectors.toList());
            mainCriteria.andIdIn(ids).andRoleEqualTo(ROLE_CA);
        }
        if (role.equals(ROLE_ADMIN)) {
            mainCriteria.andRoleIn(Arrays.asList(ROLE_USER, ROLE_CA));
        }
        if (role.equals(ROLE_SA)) {
            mainCriteria.andRoleIn(Arrays.asList(ROLE_SA, ROLE_ADMIN, ROLE_USER, ROLE_CA));
        }
        return userMapper.countByExample(example);
    }

    public void addShops(User user, List<Integer> shopIds) {
        shopIds.forEach(shopid -> {
            final UserShopKey userShopKey = new UserShopKey();
            userShopKey.setUsr(user.getId());
            userShopKey.setShop(shopid);
            userShopMapper.insert(userShopKey);
        });
        loadUserData2Redis(user);
    }

    public void alterUser(AlterUserRequest alterUserRequest) {
        final User user = userMapper.selectByPrimaryKey(alterUserRequest.getId());
        if (!Strings.isNullOrEmpty(alterUserRequest.getNickname())) {
            user.setNickname(alterUserRequest.getNickname());
            userMapper.updateByPrimaryKeySelective(user);
        }
        if (alterUserRequest.getShopIds() != null) {
            UserShopExample example = new UserShopExample();
            final UserShopExample.Criteria criteria = example.createCriteria();
            criteria.andUsrEqualTo(alterUserRequest.getId());
            userShopMapper.deleteByExample(example);
            if (!alterUserRequest.getShopIds().isEmpty()) {
                alterUserRequest.getShopIds().parallelStream().forEach(shopId -> {
                    final UserShopKey userShopKey = new UserShopKey();
                    userShopKey.setUsr(alterUserRequest.getId());
                    userShopKey.setShop(shopId);
                    userShopMapper.insert(userShopKey);
                });
            }
        }
        loadUserData2Redis(user);
    }

    public Map<String, Object> getSingle(Integer id) {
        Map<String, Object> map = new HashMap<>();
        final User user = userMapper.selectByPrimaryKey(id);
        final ResponseData<List<Shop>> myShops = orgService.getMyShops(user.getUsername());
        ResponseData<List<PostShopResponse>> orgShops = orgService.queryShopListByOrgId(user.getOrg(), 0);
        map.put("user", user);
        map.put("myShops", myShops.getResult());
        map.put("orgShops", orgShops.getResult());
        return map;
    }

    public void deleteUser(Integer id) {
        userMapper.deleteByPrimaryKey(id);
    }

    private String generateJwtToken(String userName, String userRole) {
        final Instant instant = LocalDateTime.now()
                .plus(7, ChronoUnit.DAYS)
                .atZone(ZoneId.systemDefault())
                .toInstant();
        final Date validity = Date.from(instant);

        String jwt = Jwts.builder()
                .setSubject(userName).claim("role", userRole)
                .signWith(SignatureAlgorithm.HS512, SecurityConstant.SECRET_KEY)
                .setExpiration(validity)
                .compact();
        return jwt;
    }

    private Integer decodeChannel(String base64Channel) {
        try {
            byte[] byteChannel = Base64.getDecoder().decode(base64Channel.getBytes(StandardCharsets.UTF_8));
            String decodeChannel = new String(byteChannel, StandardCharsets.UTF_8);
            return Integer.parseInt(decodeChannel);
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessCommonException("500", "渠道编号不合法");
        }
    }

    private Integer getOrgByOrgGroupId(Integer orgGroupId) {
        ResponseData<PostOrgGroupResponse> orgGroupData = orgGroupService.getOrgGroupSelf("", orgGroupId);
        if(!"200".equals(orgGroupData.getCode())) {
            throw new BusinessCommonException("500", "获取集团组织失败: " + orgGroupData.getMessage());
        }

        PostOrgGroupResponse orgGroupResponse = orgGroupData.getResult();
        if(!"org".equals(orgGroupResponse.getEntityTableName())) {
            throw new BusinessCommonException("500", "查询的ID不是一个有效的集团");
        }
        return orgGroupResponse.getEntityTableId();
    }

    private User createUserInner(String nickName, Integer orgId) {
        User user = new User();
        user.setUsername(randomLetterNumber(8));
        user.setRole(ROLE_ENDUSER);
        user.setOrg(orgId);
        user.setNickname(nickName);
        User oldUser = selectByUsername(user.getUsername());
        while (oldUser != null) {
            user.setUsername(randomLetterNumber(8));
            oldUser = selectByUsername(user.getUsername());
        }
        user.setPassword(user.getUsername());
        createUser(user);
        return user;
    }
}
