package com.keyware.shandan.datasource.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keyware.shandan.datasource.entity.DBDriverVo;
import com.keyware.shandan.datasource.enums.ConnType;
import com.keyware.shandan.datasource.enums.ErrorType;
import com.keyware.shandan.datasource.exception.DBDriverException;
import com.keyware.shandan.datasource.mapper.DBDriverMapper;
import com.keyware.shandan.datasource.service.DBDriverService;
import com.keyware.shandan.datasource.utils.DriverJarLoader;
import com.keyware.shandan.frame.properties.CustomProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * DBDriverServiceImpl
 *
 * @author GuoXin
 * @since 2021/7/28
 */
@Service
public class DBDriverServiceImpl extends ServiceImpl<DBDriverMapper, DBDriverVo> implements DBDriverService {

    @Autowired
    private CustomProperties properties;

    private final ConcurrentHashMap<String, DBDriverVo> driverCache = new ConcurrentHashMap<>(16);

    /**
     * 上传驱动
     *
     * @param file 驱动文件
     * @return -
     * @throws DBDriverException -
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public DBDriverVo upload(MultipartFile file) throws DBDriverException {
        if (file.isEmpty()) {
            throw new DBDriverException(ErrorType.DRIVER_VERIFY_FAIL);
        }
        try {
            File jar = new File(getAbsolutePath(file.getOriginalFilename()));
            if (jar.exists()) {
                deleteJar(jar);
            }
            file.transferTo(jar);
            return saveDriver(jar);
        } catch (IOException e) {
            throw new DBDriverException(ErrorType.DRIVER_SAVE_FAIL, e);
        }
    }

    /**
     * 保存驱动
     *
     * @param file -
     * @return -
     * @throws DBDriverException -
     */
    private DBDriverVo saveDriver(File file) throws DBDriverException {
        // 查找驱动类
        String driverClass = null;
        try {
            driverClass = DriverJarLoader.findDriverClassName(file.getAbsolutePath(), ConnType.JDBC);
        } catch (IOException e) {
            e.printStackTrace();
            throw new DBDriverException(ErrorType.DRIVER_VERIFY_FAIL);
        }

        if (!StringUtils.hasText(driverClass)) {
            deleteJar(file);
            throw new DBDriverException(ErrorType.DRIVER_VERIFY_FAIL);
        }

        Driver driver = null;
        try {
            driver = (Driver) Class.forName(driverClass).newInstance();
        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            deleteJar(file);
            e.printStackTrace();
            throw new DBDriverException(ErrorType.DB_DRIVER_LOAD_FAIL);
        }

        if (!driver.jdbcCompliant()) {
            deleteJar(file);
            throw new DBDriverException(ErrorType.DRIVER_VERIFY_FAIL);
        }

        DBDriverVo driverInfo = null;
        try {
            driverInfo = new DBDriverVo(file);
            driverInfo.setDriverClass(driverClass);
            saveOrUpdate(driverInfo);
        } catch (IOException e) {
            deleteJar(file);
            e.printStackTrace();
            throw new DBDriverException(ErrorType.DRIVER_SAVE_FAIL);
        }
        return driverInfo;
    }

    /**
     * @param driverId 驱动ID
     */
    @Override
    public void loadDriverJarById(String driverId) throws DBDriverException {
        DBDriverVo driverVo = driverCache.get(driverId);
        if (driverVo != null) {
            return;
        }
        // 根据驱动ID查找驱动数据
        driverVo = getById(driverId);
        if (driverVo == null) {
            throw new DBDriverException(ErrorType.DB_DRIVER_NOT_FOUND);
        }

        String driverClass = driverVo.getDriverClass();
        try {
            Thread.currentThread().getContextClassLoader().loadClass(driverClass);
        } catch (ClassNotFoundException e) {
            // 说明驱动类未被加载，需要加载驱动
            // 驱动文件
            File driverFile = new File(getAbsolutePath(driverVo.getPath()));
            if (!driverFile.exists()) {
                throw new DBDriverException(ErrorType.DB_DRIVER_NOT_FOUND);
            }

            try {
                // 加载驱动类文件
                DriverJarLoader.addUrl(driverFile);
                Thread.currentThread().getContextClassLoader().loadClass(driverClass);
                driverCache.put(driverId, driverVo);
            } catch (Exception ignore) {
                throw new DBDriverException(ErrorType.DB_DRIVER_LOAD_FAIL);
            }
        }
    }

    /**
     * 卸载指定驱动
     *
     * @param driverId
     */
    @Override
    public void unLoadDriver(String driverId) {
        DBDriverVo DBDriverVo = getById(driverId);
        try {
            for (Enumeration<Driver> drivers = DriverManager.getDrivers(); drivers.hasMoreElements(); ) {
                Driver driver = drivers.nextElement();
                if (driver.getClass().getName().equals(DBDriverVo.getDriverClass())) {
                    DriverManager.deregisterDriver(driver);
                    break;
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        driverCache.remove(driverId);
    }

    @Override
    public boolean removeById(Serializable id) {
        DBDriverVo driver = getById(id);
        deleteJar(new File(driver.getPath()));
        return super.removeById(id);
    }

    private String getAbsolutePath(String fileName) {
        String dirPath = properties.getFileStorage().getJarPath();
        File dir = new File(dirPath);
        dir.mkdirs();

        if (!StringUtils.hasText(fileName)) {
            fileName = UUID.randomUUID().toString();
        }

        return dirPath + File.separator + fileName;
    }

    /**
     * 强制删除文件
     *
     * @param file
     */
    private void deleteJar(File file) {
        if (!file.delete()) {
            System.gc();
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (!file.delete()) {
                DriverJarLoader.clear();
                file.delete();
            }
        }
    }
}
