package com.app.service.platform.auth;

import com.app.constant.JwtConstant;
import com.app.constant.RedisKeyConstant;
import com.app.dto.PageResult;
import com.app.dto.platform.SearchOnlineDto;
import com.app.entity.platform.system.Account;
import com.app.service.platform.dto.AccountDto;
import com.app.util.IpUtil;
import com.app.util.UserAgentUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 功能描述：在线账号
 *
 * @Author 梁展鹏
 */
@Slf4j
@Service
public class OnlineAccountService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 功能描述：保存在线账号
     * @param account
     * @param request
     * @param token
     * @param refreshToken
     */
    public void goOnline(Account account, HttpServletRequest request, String token, String refreshToken) {
        AccountDto _accountDto = new AccountDto();
        _accountDto.setId(account.getId());
        _accountDto.setNickname(account.getNickname());
        _accountDto.setAvatarUrl(account.getAvatarUrl());

        /* 以下为登录后的字段信息 */
        _accountDto.setIp(IpUtil.getIp(request));
        _accountDto.setIpAddress(IpUtil.getIpAddress(request));
        _accountDto.setOs(UserAgentUtil.getOperatingSystem(request));
        _accountDto.setBrowser(UserAgentUtil.getBrowserName(request));
        _accountDto.setBrowserVersion(UserAgentUtil.getBrowserVersion(request));
        _accountDto.setLoginDateTime(LocalDateTime.now());
        _accountDto.setToken(token);
        _accountDto.setRefreshToken(refreshToken);

        redisTemplate.opsForValue().set(RedisKeyConstant.PlatformAccountLogin(_accountDto.getId()), _accountDto, JwtConstant.TOKEN_EXPIRE_TIME, TimeUnit.SECONDS);
    }

    /**
     * 功能描述：查询全部数据
     */
    public List<AccountDto> getAll(String filter) {
        List<String> keys = scan();
        Collections.reverse(keys);

        List<AccountDto> list = new ArrayList<>();
        for (String key : keys) {
            AccountDto accountDto = (AccountDto) redisTemplate.opsForValue().get(key);
            if (StringUtils.hasText(filter)) {
                if (accountDto.toString().contains(filter)) {
                    list.add(accountDto);
                }
            } else {
                list.add(accountDto);
            }
        }

        return list;
    }

    /**
     * 功能描述：查询在线用户分页
     */
    public PageResult<AccountDto> page(SearchOnlineDto dto) {
        List<String> keys = scan();
        Collections.reverse(keys);

        List<AccountDto> list = new ArrayList<>();
        for (String key : keys) {
            AccountDto accountDto = (AccountDto) redisTemplate.opsForValue().get(key);
            if (StringUtils.hasText(dto.getNickname())) {
                // 过滤数据
                if (accountDto.getNickname().contains(dto.getNickname())) {
                    list.add(accountDto);
                }
            } else {
                list.add(accountDto);
            }
        }
        // 分页
        Integer current = dto.getPage();
        Integer size = dto.getSize();
        int total = list.size(); // 记录总数
        if (list.size() == 0) {
            return new PageResult<>(current, size, (long) total, list);
        }
        Integer pageCount = 0; // 页数
        if (total % size == 0) {
            pageCount = total / size;
        } else {
            pageCount = total / size + 1;
        }

        int fromIndex = 0; // 开始截取
        int toIndex = 0; // 结束截取

        if (current != pageCount) {
            fromIndex = (current - 1) * size;
            toIndex = fromIndex + size;
        } else {
            fromIndex = (current - 1) * size;
            toIndex = total;
        }
        List<AccountDto> pageList = list.subList(fromIndex, toIndex);
        return new PageResult<>(current, size, (long) total, pageList);
    }

    /**
     * 功能描述：踢出用户
     */
    public void kickOffLine(String accountId) {
        redisTemplate.delete(RedisKeyConstant.PlatformAccountLogin(accountId));
    }

    /**
     * 功能描述：退出登录
     */
    public void logout(String accountId) {
        redisTemplate.delete(RedisKeyConstant.PlatformAccountLogin(accountId));
    }

    /**
     * 功能描述：查询用户
     */
    public AccountDto get(String accountId) {
        return (AccountDto) redisTemplate.opsForValue().get(RedisKeyConstant.PlatformAccountLogin(accountId));
    }

    /**
     * 功能描述：检测用户是否在之前已经登录，已经登录踢下线
     */
    public void checkLogin(Account account, String token) {
        List<AccountDto> list = getAll(account.getId());
        if (CollectionUtils.isEmpty(list)) {
            return;
        }

        for (AccountDto _accountDto : list) {
            if (StringUtils.hasText(_accountDto.getToken()) && !_accountDto.getToken().equals(token)) {
                kickOffLine(_accountDto.getId());
            } else if (!StringUtils.hasText(token)) {
                kickOffLine(account.getId());
            }
        }
    }

    /**
     * 功能描述：查找匹配key
     */
    private List<String> scan() {
        ScanOptions options = ScanOptions.scanOptions().match(RedisKeyConstant.PlatformAccountLogin("*")).build();
        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
        RedisConnection rc = Objects.requireNonNull(factory).getConnection();
        Cursor<byte[]> cursor = rc.scan(options);
        List<String> result = new ArrayList<>();
        while (cursor.hasNext()) {
            result.add(new String(cursor.next()));
        }
        try {
            RedisConnectionUtils.releaseConnection(rc, factory);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

}
