package com.yonge.toolset.payment.util;

import com.yonge.toolset.base.exception.BizException;
import com.yonge.toolset.base.util.StringUtil;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Objects;
import java.util.concurrent.*;
import java.util.function.Function;

/**
 * 分布式竞争锁
 *
 * @author hgw
 */
public class DistributedLock {
    private final static Logger log = LoggerFactory.getLogger(DistributedLock.class);

    private final RedissonClient redissonClient;

    private static final long DEFAULT_TIMEOUT = 60L;// key过期时间默认1分钟后

    private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;//默认过期时间单位-秒钟

    private DistributedLock(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    public static DistributedLock of(RedissonClient redissonClient) {
        return new DistributedLock(redissonClient);
    }

    /**
     * 分布式锁-默认锁60秒
     *
     * @param lockName lockKey
     * @param runnable 任务
     * @return true 锁成功  false 锁失败
     */
    public boolean runIfLockCanGet(final String lockName, Runnable runnable) {
        return runIfLockCanGet(lockName, runnable, DEFAULT_TIMEOUT, DEFAULT_TIME_UNIT);
    }

    /**
     * 分布式锁
     *
     * @param lockName lockKey
     * @param runnable 任务
     * @param timeout  超时时间
     * @param unit     超时时间单位
     * @return true 锁成功  false 锁失败
     */
    public boolean runIfLockCanGet(final String lockName, Runnable runnable, final long timeout, TimeUnit unit) {
        RLock lock = redissonClient.getLock(lockName);
        if (Objects.isNull(lock)) {
            log.info("runIfLockCanGet lock is null lockName : {}", lockName);
            return false;
        }
        try {
            if (lock.tryLock(0, timeout, unit)) {
                if (Objects.nonNull(runnable)) {
                    runnable.run();
                }
                return true;
            } else {
                return false;
            }
        } catch (BizException e) {
            throw e;
        }  catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("runIfLockCanGet error lockName :" + lockName, e);
        } finally {
            unlock(lock);
        }
    }

    /**
     * 分布式锁-同步
     *
     * @param lockName lockKey
     * @param callable 任务
     * @param timeout  超时时间
     * @param unit     超时时间单位
     * @return Future 异步任务
     */
    public <T> T runIfLockCanGet(final String lockName, Callable<T> callable, final long timeout, TimeUnit unit) {
        RLock lock = redissonClient.getLock(lockName);
        if (Objects.isNull(lock)) {
            log.info("callIfLockCanGet lock is null lockName : {}", lockName);
            return null;
        }
        ExecutorService executor = Executors.newCachedThreadPool();
        try {
            if (lock.tryLock(0, timeout, unit)) {
                log.info("callIfLockCanGet lock lockName : {} time is {}", lockName, System.currentTimeMillis());
                Future<T> submit = executor.submit(callable);
                return submit.get();
            } else {
                return null;
            }
        } catch (BizException e) {
            throw e;
        } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            cause.printStackTrace();
            ;

            String message = cause.getMessage();
            if (StringUtil.isEmpty(message)) {
                throw new RuntimeException("任务执行异常");
            } else {
                throw new BizException(message);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("任务执行异常");
        } finally {
            executor.shutdown();
            unlock(lock);
        }
    }

    /**
     * 分布式锁-异步
     *
     * @param lockName lockKey
     * @param callable 任务
     * @param timeout  超时时间
     * @param unit     超时时间单位
     * @return Future 异步任务
     */
    public <T> Future<T> callIfLockCanGet(final String lockName, Callable<T> callable, final long timeout, TimeUnit unit) {
        RLock lock = redissonClient.getLock(lockName);
        if (Objects.isNull(lock)) {
            log.info("callIfLockCanGet lock is null lockName : {}", lockName);
            return null;
        }
        ExecutorService executor = Executors.newCachedThreadPool();
        try {
            if (lock.tryLock(0, timeout, unit)) {
                log.info("callIfLockCanGet lock lockName : {} time is {}", lockName, System.currentTimeMillis());
                return executor.submit(callable);
            } else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("任务执行异常");
        } finally {
            executor.shutdown();
            unlock(lock);
        }
    }

    /**
     * 解锁
     */
    public void unlock(RLock lock) {
        if (lock.getHoldCount() != 0 && lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }


    /**
     * 分布式锁-同步
     *
     * @param lockName lockKey
     * @param function 需要执行的方法
     * @return Future
     */
    public <T, R> R runIfLockToFunction(final String lockName, Function<T, R> function, T param, long waitTime) {
        RLock lock = redissonClient.getLock(lockName);
        if (Objects.isNull(lock)) {
            log.info("callIfLockCanGet lock is null lockName : {}", lockName);
            return null;
        }
        try {
            if (lock.tryLock(waitTime, 60, TimeUnit.SECONDS)) {
                log.info("callIfLockCanGet lock lockName : {} time is {}", lockName, System.currentTimeMillis());
                return function.apply(param);
            } else {
                return null;
            }
        } catch (BizException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("任务执行异常");
        } finally {
            unlock(lock);
        }
    }

}
