package com.ecep.contract.ds.company.service;

import java.io.File;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;

import com.ecep.contract.util.FileUtils;
import org.hibernate.Hibernate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.StringUtils;

import com.ecep.contract.CompanyCustomerFileType;
import com.ecep.contract.CompanyVendorFileType;
import com.ecep.contract.SpringApp;
import com.ecep.contract.ds.company.CompanyFileUtils;
import com.ecep.contract.ds.other.repository.HolidayTableRepository;
import com.ecep.contract.model.Company;
import com.ecep.contract.model.CompanyBasicFile;
import com.ecep.contract.model.CompanyCustomerFile;
import com.ecep.contract.model.CompanyVendorFile;
import com.ecep.contract.model.HolidayTable;

public abstract class CompanyBasicService {
    private static final Logger logger = LoggerFactory.getLogger(CompanyBasicService.class);

    /**
     * 调整日期到工作日
     *
     * @param date 要调整的日期
     * @return 调整的日期
     */
    public static LocalDate adjustToWorkDay(LocalDate date) {
        if (date.getDayOfWeek() == DayOfWeek.SATURDAY) {
            // 再减去1天，到周五
            date = date.plusDays(-1);
        } else if (date.getDayOfWeek() == DayOfWeek.SUNDAY) {
            // 再减去2天，到周五
            date = date.plusDays(-2);
        }

        HolidayTableRepository holidayTableRepository = SpringApp.getBean(HolidayTableRepository.class);
        //TODO 跳过节假日
        int tryDays = 15;
        while (tryDays-- > 0) {
            HolidayTable holidayTable = holidayTableRepository.findById(date).orElse(null);
            if (holidayTable == null) {
                // 没有节假日定义，检查是否是工作日
                DayOfWeek dayOfWeek = date.getDayOfWeek();
                if (dayOfWeek == DayOfWeek.SATURDAY) {
                    date = date.plusDays(-1);
                    continue;
                }
                if (dayOfWeek == DayOfWeek.SUNDAY) {
                    date = date.plusDays(-2);
                    continue;
                }
                break;
            }
            if (!holidayTable.isHoliday()) {
                // 不是节假日
                break;
            }
            date = date.plusDays(-1);
        }

        return date;
    }

    @Lazy
    @Autowired
    protected CompanyService companyService;


    protected boolean isEditableFile(String fileName) {
        return FileUtils.withExtensions(fileName,
                FileUtils.XLS, FileUtils.XLSX,
                FileUtils.DOC, FileUtils.DOCX);
    }

    protected boolean isArchiveFile(String fileName) {
        return FileUtils.withExtensions(fileName,
                FileUtils.PNG, FileUtils.PDF,
                FileUtils.JPG, FileUtils.JPEG);
    }

    public abstract <T, F extends CompanyBasicFile<T>, ID> void deleteFile(F file);

    protected <T, F extends CompanyBasicFile<T>, ID> boolean fetchDbFiles(List<F> dbFiles, Map<String, F> map, Consumer<String> status) {
        boolean modified = false;
        List<File> editFiles = new ArrayList<>();
        // 排除掉数据库中重复的
        for (F dbFile : dbFiles) {
            String filePath = dbFile.getFilePath();
            // 没有文件信息，无效记录，删除
            if (!StringUtils.hasText(filePath)) {
                deleteFile(dbFile);
                continue;
            }

            // 文件不存在或者隐藏文件，删除记录
            File file = new File(filePath);
            if (!file.exists() || CompanyFileUtils.isHiddenFile(file)) {
                deleteFile(dbFile);
                continue;
            }

            // old 是冲突的，按dbFiles顺序，旧的（list前面的）
            F old = map.put(filePath, dbFile);
            // 目录有重复删除
            if (old != null) {
                deleteFile(old);
            }

            String editFilePath = dbFile.getEditFilePath();
            if (!Objects.equals(filePath, editFilePath)) {
                // 没有文件信息，无效记录，删除
                if (StringUtils.hasText(editFilePath)) {
                    File editFile = new File(editFilePath);
                    if (editFile.exists()) {
                        editFiles.add(editFile);
                    } else {
                        dbFile.setEditFilePath("");
                    }
                }
            }

            // 类型未设置，补充类型
            if (dbFile.getType() == null) {
                if (fillFileAsDefaultType(dbFile, file, status)) {
                    modified = true;
                }
            }
        }

        for (File editFile : editFiles) {
            String editFilePath = editFile.getAbsolutePath();
            F dup = map.remove(editFilePath);
            // 目录有重复删除
            if (dup != null) {
                deleteFile(dup);
            }
        }

        return modified;
    }

    /**
     * dbFile 的 Type 未设置时，设置为默认类型
     *
     * @param dbFile 要设置的 对象
     * @param file   相关文件对象
     * @param status 状态输出
     * @param <T>    类型类
     * @param <F>    文件类
     * @return 是否修改了
     * @see CompanyVendorFile
     * @see CompanyVendorFileType
     * @see CompanyCustomerFile
     * @see CompanyCustomerFileType
     */
    protected abstract <T, F extends CompanyBasicFile<T>> boolean fillFileAsDefaultType(F dbFile, File file, Consumer<String> status);


    protected void moveFileToCompany(Company company, List<File> needMoveToCompanyPath) {
        if (needMoveToCompanyPath.isEmpty()) {
            return;
        }
        if (!Hibernate.isInitialized(company)) {
            company = companyService.findById(company.getId());
        }
        String companyPath = company.getPath();
        if (!StringUtils.hasText(companyPath)) {
            return;
        }
        for (File file : needMoveToCompanyPath) {
            File dest = new File(companyPath, file.getName());
            if (file.renameTo(dest)) {
                //
                if (logger.isInfoEnabled()) {
                    logger.info("{} -> {}", file.getAbsolutePath(), dest.getAbsolutePath());
                }
            } else {
                //
                if (dest.exists()) {
                    if (file.delete()) {
                        if (logger.isInfoEnabled()) {
                            logger.info("Delete File {}", file.getAbsolutePath());
                        }
                    }
                }
            }
        }

        // 公司目录下文件更新后，待公司文件自行处理

    }

    /**
     * 遍历扫描 path 目录下的文件，
     *
     * @param path
     * @param needMoveToCompanyPath
     * @param retrieveFiles
     * @param map
     * @param status
     * @param <T>
     * @param <F>
     */
    protected <T, F extends CompanyBasicFile<T>> void fetchFiles(
            String path,
            List<File> needMoveToCompanyPath,
            List<F> retrieveFiles,
            Map<String, F> map,
            Consumer<String> status
    ) {
        if (!StringUtils.hasText(path)) {
            return;
        }
        File dir = new File(path);
        if (!dir.exists()) {
            return;
        }
        File[] files = dir.listFiles();
        if (files == null) {
            return;
        }
        List<File> step1 = new ArrayList<>();
        for (File file : files) {
            // 只处理文件
            if (!file.isFile() || CompanyFileUtils.isHiddenFile(file)) {
                continue;
            }

            if (CompanyFileUtils.isCompanyFile(file)) {
                needMoveToCompanyPath.add(file);
                continue;
            }
            // 先把所有文件加到列表中
            step1.add(file);
        }

        if (!step1.isEmpty()) {
            // 第一步，处理已经存在的
            for (File file : new ArrayList<>(step1)) {
                String filePath = file.getAbsolutePath();
                if (map.containsKey(filePath)) {
                    // 已记录
                    F customerFile = map.get(filePath);
                    if (fillFile(customerFile, file, step1, status)) {
                        retrieveFiles.add(customerFile);
                    }
                    step1.remove(file);
                }
            }

            // 第二步骤，再处理
            while (!step1.isEmpty()) {
                File file = step1.removeLast();
                F filled = fillFileType(file, step1, status);
                if (filled != null) {
                    retrieveFiles.add(filled);
                }
            }
        }
    }

    /**
     * 填充文件类型
     *
     * @param file
     * @param fileList
     * @param status   状态输出
     * @param <T>      类型类
     * @param <F>      文件类
     */
    protected abstract <T, F extends CompanyBasicFile<T>> F fillFileType(File file, List<File> fileList, Consumer<String> status);

    /**
     * @param customerFile 文件对象
     * @param file         相关文件
     * @param fileList     待处理文件列表
     * @param status       状态输出
     * @param <T>          类型类
     * @param <F>          文件类
     * @return true 有修改
     */
    protected <T, F extends CompanyBasicFile<T>> boolean fillFile(F customerFile, File file, List<File> fileList, Consumer<String> status) {
        String fileName = file.getName();
        boolean modified = CompanyFileUtils.fillApplyDateAbsent(file, customerFile, F::getSignDate, F::setSignDate);
        // 评估表
        if (isEvaluationFile(fileName)) {
            if (fillFileAsEvaluationFile(customerFile, file, fileList, status)) {
                modified = true;
            }
        } else {
            if (!Objects.equals(file.getAbsolutePath(), customerFile.getFilePath())) {
                customerFile.setFilePath(file.getAbsolutePath());
                modified = true;
            }
        }
        return modified;
    }

    /**
     * 以评价表单模板。填充文件信息
     *
     * @param customerFile 文件对象
     * @param file         相关文件
     * @param fileList     待处理文件列表
     * @param status       状态输出
     * @param <T>          类型类
     * @param <F>          文件类
     * @return true：文件对象有修改，否则返回false
     */
    protected <T, F extends CompanyBasicFile<T>> boolean fillFileAsEvaluationFile(F customerFile, File file, List<File> fileList, Consumer<String> status) {
        boolean modified = setFileTypeAsEvaluationForm(customerFile);
        String fileName = file.getName();

        // 文件全路径
        String fileAbsolutePath = file.getAbsolutePath();

        if (isEditableFile(fileName)) {
            // 是可编辑文件时
            if (!Objects.equals(fileAbsolutePath, customerFile.getEditFilePath())) {
                customerFile.setEditFilePath(fileAbsolutePath);
                modified = true;
            }

            if (useAsEditableFile(customerFile, file, fileList, status)) {
                modified = true;
            }
        } else if (isArchiveFile(fileName)) {
            // 存档文件时
            if (!Objects.equals(fileAbsolutePath, customerFile.getFilePath())) {
                customerFile.setFilePath(fileAbsolutePath);
                modified = true;
            }

            if (useAsArchiveFile(customerFile, file, fileList, status)) {
                modified = true;
            }
        } else {
            customerFile.setFilePath(file.getAbsolutePath());
        }
        return modified;
    }

    private <T, F extends CompanyBasicFile<T>> boolean useAsEditableFile(F customerFile, File file, List<File> fileList, Consumer<String> status) {
        if (fileList == null) {
            return false;
        }

        // 检查存档文件
        if (StringUtils.hasText(customerFile.getFilePath())) {
            // 移除 存档文件
            File archiveFile = new File(customerFile.getFilePath());
            fileList.remove(archiveFile);
            return false;
        }

        // 未关联存档文件，去找 fileList 查
        if (fileList.isEmpty()) {
            // 文件全路径
            String fileAbsolutePath = file.getAbsolutePath();
            // 当没有其他文件时，与 EditFile 相同
            if (!Objects.equals(fileAbsolutePath, customerFile.getFilePath())) {
                customerFile.setFilePath(fileAbsolutePath);
                return true;
            }
            return false;
        }

        File archiveFile = null;
        // 文件名
        String fileName = file.getName();
        // 文件名，不含后缀
        String name = StringUtils.stripFilenameExtension(fileName);
        for (File f : fileList) {
            // 查找存档文件
            if (f.getName().startsWith(name) && isArchiveFile(f.getName())) {
                archiveFile = f;
                break;
            }
        }
        // 没有匹配到文件
        if (archiveFile == null) {
            return false;
        }

        if (fileList.remove(archiveFile)) {
            customerFile.setFilePath(archiveFile.getAbsolutePath());
            return true;
        }

        return false;
    }

    private <T, F extends CompanyBasicFile<T>> boolean useAsArchiveFile(F customerFile, File file, List<File> fileList, Consumer<String> status) {
        if (fileList == null) {
            return false;
        }

        // 未关联存档文件，去找 fileList 查
        if (fileList.isEmpty()) {
            return false;
        }

        // 检查可编辑文件
        if (StringUtils.hasText(customerFile.getEditFilePath())) {
            // 移除 可编辑文件
            File editFile = new File(customerFile.getEditFilePath());
            fileList.remove(editFile);
            return false;
        }

        File editFile = null;
        // 文件名
        String fileName = file.getName();
        // 文件名，不含后缀
        String name = StringUtils.stripFilenameExtension(fileName);
        for (File f : fileList) {
            // 查找存档文件
            if (f.getName().startsWith(name) && isEditableFile(f.getName())) {
                editFile = f;
                break;
            }
        }
        // 没有匹配到文件
        if (editFile == null) {
            return false;
        }

        if (fileList.remove(editFile)) {
            String editFilePath = editFile.getAbsolutePath();
            if (!Objects.equals(editFilePath, customerFile.getEditFilePath())) {
                customerFile.setEditFilePath(editFilePath);
                return true;
            }
        }

        return false;
    }


    /**
     * 设置文件类型为表单文件
     *
     * @param file 文件对象
     * @param <T>  类型类
     * @param <F>  文件类
     * @return true 文件对象有修改，否则false
     */
    protected abstract <T, F extends CompanyBasicFile<T>> boolean setFileTypeAsEvaluationForm(F file);

    /**
     * 判定 参数 fileName 是否是一个评价表文件
     *
     * @param fileName 文件名称
     * @return true 是评价表，否则false
     */
    protected abstract boolean isEvaluationFile(String fileName);


}
