package com.kingdee.eas.custom.integration.beisen.service.impl;

import com.kingdee.bos.BOSException;
import com.kingdee.bos.Context;
import com.kingdee.bos.metadata.entity.EntityViewInfo;
import com.kingdee.bos.metadata.entity.FilterInfo;
import com.kingdee.bos.metadata.entity.FilterItemCollection;
import com.kingdee.bos.metadata.entity.FilterItemInfo;
import com.kingdee.eas.basedata.org.*;
import com.kingdee.eas.basedata.person.IPerson;
import com.kingdee.eas.basedata.person.PersonCollection;
import com.kingdee.eas.basedata.person.PersonFactory;
import com.kingdee.eas.basedata.person.PersonInfo;
import com.kingdee.eas.custom.integration.base.util.SHRApiUtils;
import com.kingdee.eas.custom.integration.beisen.dto.AttendanceRequest;
import com.kingdee.eas.custom.integration.beisen.dto.AttendanceResponse;
import com.kingdee.eas.custom.integration.beisen.dto.BatchAttendanceRequest;
import com.kingdee.eas.custom.integration.beisen.dto.FailDetail;
import com.kingdee.eas.custom.integration.beisen.enums.ReportType;
import com.kingdee.eas.custom.integration.beisen.service.AttendanceService;
import com.kingdee.eas.custom.integration.beisen.validator.AttendanceValidator;
import com.kingdee.eas.hr.ats.*;
import com.kingdee.eas.hr.ats.app.AtsSumTypeEnum;
import com.kingdee.eas.util.app.DbUtil;
import com.kingdee.shr.base.syssetting.BaseItemStateEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 考勤数据批量写入服务实现
 * <p>
 * 支持批量写入、部分成功。
 */
@Service
public class AttendanceServiceImpl implements AttendanceService {

    private static final Logger logger = LoggerFactory.getLogger(AttendanceServiceImpl.class);
    private final String HRORGUNIT_ID = "00000000-0000-0000-0000-000000000000CCE7AED4";

    /**
     * 考勤日报批量写入
     *
     * @param request 考勤日报批量写入请求
     * @return
     */
    @Override
    public AttendanceResponse writeDailyAttendance(BatchAttendanceRequest request) {
        logger.info("开始批量写入考勤日报 - 记录数: {}",
                request.getRecords() != null ? request.getRecords().size() : 0);

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        AttendanceResponse response = new AttendanceResponse();
        List<AttendanceRequest> records = request.getRecords();

        try {
            Context ctx = SHRApiUtils.getContext();
            IAttendanceResult iAttendanceResult = AttendanceResultFactory.getLocalInstance(ctx);
            IPerson iPerson = PersonFactory.getLocalInstance(ctx);
            IAdminOrgUnit iAdminOrgUnit = AdminOrgUnitFactory.getLocalInstance(ctx);
            IPosition iPosition = PositionFactory.getLocalInstance(ctx);
            Map<String, AttendanceProjectInfo> projectMap = getAttendanceProjectMap(ctx);

            Map<String, AdminOrgUnitInfo> orgCache = new HashMap<>();
            Map<String, PositionInfo> posCache = new HashMap<>();

            int totalCount = records.size();
            AtomicInteger successCount = new AtomicInteger(0);
            AtomicInteger failCount = new AtomicInteger(0);

            for (AttendanceRequest record : records) {
                try {
                    String identifier = buildFailIdentifier(record);
                    String dateStr = record.getAttendanceDate();

                    // 校验
                    String validateError = AttendanceValidator.validateAndGetError(record, ReportType.DAILY);
                    if (validateError != null) {
                        recordFail(response, failCount, identifier, "校验失败: " + validateError);
                        logger.error("考勤日报记录校验失败 - 标识: {}, 原因: {}", identifier, validateError);
                        continue;
                    }

                    Date attendanceDate = sdf.parse(dateStr);

                    // 查找前置数据
                    LookupResult<PersonInfo> personR = lookupPerson(iPerson, record.getPersonNumber());
                    if (personR.error != null) {
                        recordFail(response, failCount, identifier, personR.error);
                        logger.error("考勤日报写入失败 - 工号: {}, 日期: {}, 原因: {}",
                                record.getPersonNumber(), dateStr, personR.error);
                        continue;
                    }

                    LookupResult<AdminOrgUnitInfo> orgR = lookupAdminOrg(iAdminOrgUnit, record.getAdminOrgCode(), orgCache);
                    if (orgR.error != null) {
                        recordFail(response, failCount, identifier, orgR.error);
                        logger.error("考勤日报写入失败 - 工号: {}, 日期: {}, 原因: {}",
                                record.getPersonNumber(), dateStr, orgR.error);
                        continue;
                    }

                    LookupResult<PositionInfo> posR = lookupPosition(iPosition, record.getPositionCode(), posCache);
                    if (posR.error != null) {
                        recordFail(response, failCount, identifier, posR.error);
                        logger.error("考勤日报写入失败 - 工号: {}, 日期: {}, 原因: {}",
                                record.getPersonNumber(), dateStr, posR.error);
                        continue;
                    }

                    // 查/建考勤记录
                    FilterInfo filter = new FilterInfo();
                    FilterItemCollection filterItems = filter.getFilterItems();
                    filterItems.add(new FilterItemInfo("proposer", personR.data.getId()));
                    filterItems.add(new FilterItemInfo("attenceDate", attendanceDate));
                    EntityViewInfo viewInfo = EntityViewInfo.getInstance(filter, null, null);

                    AttendanceResultCollection resultCol = iAttendanceResult.getAttendanceResultCollection(viewInfo);
                    AttendanceResultInfo resultInfo;

                    if (resultCol.isEmpty()) {
                        resultInfo = new AttendanceResultInfo();
                        resultInfo.setProposer(personR.data);
                        resultInfo.setAdminOrgUnit(orgR.data);
                        resultInfo.setAttadminOrgUnit(orgR.data);
                        resultInfo.setAttenceDate(attendanceDate);
                        resultInfo.setAttenceStatus(AttendanceStatus.hasCalculate);
                        resultInfo.put("atthrOrgUnit", HRORGUNIT_ID);
                        resultInfo.put("hrOrgUnit", HRORGUNIT_ID);
                        resultInfo.setAttposition(posR.data);
                        resultInfo.setPosition(posR.data);
                        resultInfo.setAvailable(true);
                        resultInfo.setConfirmState(AttenceConfirmState.autoConfirm);
                        resultInfo.setIsDefaultManage(true);
                        iAttendanceResult.save(resultInfo);
                    } else {
                        resultInfo = resultCol.get(0);
                        if (AttendanceStatus.hasAudit.equals(resultInfo.getAttenceStatus())) {
                            String reason = "考勤明细已审核，不允许更新";
                            recordFail(response, failCount, identifier, reason);
                            logger.error("考勤日报写入失败 - 工号: {}, 日期: {}, 原因: {}",
                                    record.getPersonNumber(), dateStr, reason);
                            continue;
                        }
                    }

                    // 构建更新考勤明细 SQL并执行
                    LookupResult<String> sqlR = buildDailyUpdateSql(resultInfo, record.getDynamicParams(), projectMap);
                    if (sqlR.error != null) {
                        recordFail(response, failCount, identifier, sqlR.error);
                        logger.error("考勤日报写入失败 - 工号: {}, 日期: {}, 原因: {}",
                                record.getPersonNumber(), dateStr, sqlR.error);
                        continue;
                    }
                    logger.info("考勤日报写入SQL - {}", sqlR.data);
                    // 执行SQL
                    DbUtil.execute(ctx, sqlR.data);
                    // 更新成功 加1
                    successCount.incrementAndGet();
                    logger.info("考勤日报写入成功 - 工号: {}, 日期: {}", record.getPersonNumber(), dateStr);
                } catch (Exception e) {
                    //失败 加1
                    failCount.incrementAndGet();
                    String identifier = buildFailIdentifier(record);
                    response.addFailDetail(new FailDetail(identifier, "系统内部错误，请反馈管理员处理"));
                    logger.warn("考勤日报写入失败 - 工号: {}, 日期: {}, 原因: {}",
                            record.getPersonNumber(), record.getAttendanceDate(), e.getMessage());
                }
            }

            response.setTotalCount(totalCount);
            response.setSuccessCount(successCount.get());
            response.setFailCount(failCount.get());
            logger.info("考勤日报批量写入完成 - 总条数: {}, 成功: {}, 失败: {}",
                    totalCount, successCount.get(), failCount.get());

        } catch (BOSException e) {
            throw new RuntimeException(e);
        }
        return response;
    }


    /**
     * 考勤月报批量写入
     *
     * @param request 考勤月报批量写入请求
     * @return
     */
    @Override
    public AttendanceResponse writeMonthlyAttendance(BatchAttendanceRequest request) {
        logger.info("开始批量写入考勤月报 - 记录数: {}",
                request.getRecords() != null ? request.getRecords().size() : 0);

        AttendanceResponse response = new AttendanceResponse();
        List<AttendanceRequest> records = request.getRecords();

        if (records == null || records.isEmpty()) {
            logger.warn("考勤月报批量写入 - 记录列表为空");
            return response;
        }

        try {
            Context ctx = SHRApiUtils.getContext();
            IAttendanceResultSummary iResultSummary =
                    AttendanceResultSummaryFactory.getLocalInstance(ctx);
            IPerson iPerson = PersonFactory.getLocalInstance(ctx);
            IAdminOrgUnit iAdminOrgUnit = AdminOrgUnitFactory.getLocalInstance(ctx);
            IPosition iPosition = PositionFactory.getLocalInstance(ctx);
            IAttendancePeriod iPeriod = AttendancePeriodFactory.getLocalInstance(ctx);
            Map<String, AttendanceProjectInfo> projectMap = getAttendanceProjectMap(ctx);

            Map<String, AttendancePeriodInfo> periodCache = new HashMap<>();
            Map<String, AdminOrgUnitInfo> orgCache = new HashMap<>();
            Map<String, PositionInfo> posCache = new HashMap<>();

            int totalCount = records.size();
            // 成功计数器
            AtomicInteger successCount = new AtomicInteger(0);
            // 失败计数器
            AtomicInteger failCount = new AtomicInteger(0);

            for (AttendanceRequest record : records) {
                try {
                    // 构建失败标识
                    String identifier = buildFailIdentifier(record);
                    String periodCode = record.getAttendancePeriod();

                    // 校验
                    String validateError = AttendanceValidator.validateAndGetError(record, ReportType.MONTHLY);
                    if (validateError != null) {
                        recordFail(response, failCount, identifier, "校验失败: " + validateError);
                        logger.error("考勤月报记录校验失败 - 标识: {}, 原因: {}", identifier, validateError);
                        continue;
                    }

                    // 查找前置数据
                    LookupResult<AttendancePeriodInfo> periodR = lookupPeriod(iPeriod, periodCode, periodCache);
                    if (periodR.error != null) {
                        recordFail(response, failCount, identifier, periodR.error);
                        logger.error("考勤月报写入失败 - 工号: {}, 周期: {}, 原因: {}",
                                record.getPersonNumber(), periodCode, periodR.error);
                        continue;
                    }

                    LookupResult<PersonInfo> personR = lookupPerson(iPerson, record.getPersonNumber());
                    if (personR.error != null) {
                        recordFail(response, failCount, identifier, personR.error);
                        logger.warn("考勤月报写入失败 - 工号: {}, 周期: {}, 原因: {}",
                                record.getPersonNumber(), periodCode, personR.error);
                        continue;
                    }

                    LookupResult<AdminOrgUnitInfo> orgR = lookupAdminOrg(iAdminOrgUnit, record.getAdminOrgCode(), orgCache);
                    if (orgR.error != null) {
                        recordFail(response, failCount, identifier, orgR.error);
                        logger.warn("考勤月报写入失败 - 工号: {}, 周期: {}, 原因: {}",
                                record.getPersonNumber(), periodCode, orgR.error);
                        continue;
                    }

                    LookupResult<PositionInfo> posR = lookupPosition(iPosition, record.getPositionCode(), posCache);
                    if (posR.error != null) {
                        recordFail(response, failCount, identifier, posR.error);
                        logger.warn("考勤月报写入失败 - 工号: {}, 周期: {}, 原因: {}",
                                record.getPersonNumber(), periodCode, posR.error);
                        continue;
                    }

                    // 查/建考勤月报汇总
                    FilterInfo filter = new FilterInfo();
                    FilterItemCollection filterItems = filter.getFilterItems();
                    filterItems.add(new FilterItemInfo("proposer", personR.data.getId()));
                    filterItems.add(new FilterItemInfo("attendancePeriod", periodR.data.getId()));
                    filterItems.add(new FilterItemInfo("adminOrgUnit", orgR.data.getId()));
                    filterItems.add(new FilterItemInfo("position", posR.data.getId()));
                    EntityViewInfo viewInfo = EntityViewInfo.getInstance(filter, null, null);

                    AttendanceResultSummaryCollection resultCol = iResultSummary.getAttendanceResultSummaryCollection(viewInfo);
                    AttendanceResultSummaryInfo resultInfo;

                    if (resultCol.isEmpty()) {
                        resultInfo = new AttendanceResultSummaryInfo();
                        resultInfo.setProposer(personR.data);
                        resultInfo.setAttendancePeriod(periodR.data);
                        resultInfo.setSalaryStatus(AttenceSalaryStatus.unAudit);
                        resultInfo.setSumType(AtsSumTypeEnum.person);
                        resultInfo.setAdminOrgUnit(orgR.data);
                        resultInfo.setAttadminOrgUnit(orgR.data);
                        resultInfo.setPosition(posR.data);
                        resultInfo.put("atthrOrgUnit", HRORGUNIT_ID);
                        resultInfo.put("hrOrgUnit", HRORGUNIT_ID);
                        resultInfo.setAvailable(true);
                        iResultSummary.save(resultInfo);
                    } else {
                        resultInfo = resultCol.get(0);
                        if (!AttenceSalaryStatus.unAudit.equals(resultInfo.getSalaryStatus())) {
                            String reason = "考勤月报汇总已审核，不允许更新";
                            recordFail(response, failCount, identifier, reason);
                            logger.error("考勤月报写入失败 - 工号: {}, 周期: {}, 原因: {}",
                                    record.getPersonNumber(), periodCode, reason);
                            continue;
                        }
                    }

                    // 构建UPDATE SQL并执行
                    LookupResult<String> sqlR = buildMonthlyUpdateSql(resultInfo, record.getDynamicParams(), projectMap);
                    if (sqlR.error != null) {
                        recordFail(response, failCount, identifier, sqlR.error);
                        logger.error("考勤月报写入失败 - 工号: {}, 周期: {}, 原因: {}",
                                record.getPersonNumber(), periodCode, sqlR.error);
                        continue;
                    }
                    logger.debug("考勤月报写入SQL - {}", sqlR.data);
                    // 执行SQL
                    DbUtil.execute(ctx, sqlR.data);
                    // 成功加1
                    successCount.incrementAndGet();
                    logger.debug("考勤月报写入成功 - 工号: {}, 周期: {}", record.getPersonNumber(), periodCode);

                } catch (Exception e) {
                    // 失败加1
                    failCount.incrementAndGet();
                    String identifier = buildFailIdentifier(record);
                    String reason = e.getMessage() != null ? e.getMessage() : "未知错误";
                    response.addFailDetail(new FailDetail(identifier, reason));
                    logger.error("考勤月报写入失败 - 工号: {}, 周期: {}, 原因: {}",
                            record.getPersonNumber(), record.getAttendancePeriod(), reason);
                }
            }

            response.setTotalCount(totalCount);
            response.setSuccessCount(successCount.get());
            response.setFailCount(failCount.get());
            logger.info("考勤月报批量写入完成 - 总条数: {}, 成功: {}, 失败: {}",
                    totalCount, successCount.get(), failCount.get());

        } catch (BOSException e) {
            throw new RuntimeException(e);
        }
        return response;
    }

    /**
     * 记录失败
     *
     * @param response
     * @param failCount
     * @param identifier
     * @param reason
     */
    private void recordFail(
            AttendanceResponse response,
            AtomicInteger failCount,
            String identifier,
            String reason) {
        // 失败加1
        failCount.incrementAndGet();
        // 添加失败详情
        response.addFailDetail(new FailDetail(identifier, reason));
    }

    /**
     * 根据工号查找人员
     *
     * @param iPerson
     * @param personNumber
     * @return
     * @throws BOSException
     */
    private LookupResult<PersonInfo> lookupPerson(
            IPerson iPerson,
            String personNumber
    ) throws BOSException {
        PersonCollection personCollection = iPerson.getPersonCollection(
                "where number = '" + escapeSql(personNumber) + "'");
        if (personCollection == null || personCollection.isEmpty()) {
            return LookupResult.fail("sHR系统中不存在工号为" + personNumber + "的人员");
        }
        return LookupResult.ok(personCollection.get(0));
    }

    /**
     * 根据编码查找行政组织
     *
     * @param iAdminOrgUnit
     * @param adminOrgCode
     * @param cache
     * @return
     * @throws BOSException
     */
    private LookupResult<AdminOrgUnitInfo> lookupAdminOrg(
            IAdminOrgUnit iAdminOrgUnit,
            String adminOrgCode,
            Map<String, AdminOrgUnitInfo> cache
    ) throws BOSException {
        AdminOrgUnitInfo info = cache.get(adminOrgCode);
        if (info == null) {
            AdminOrgUnitCollection col = iAdminOrgUnit.getAdminOrgUnitCollection(
                    "where number = '" + escapeSql(adminOrgCode) + "'");
            if (col != null && !col.isEmpty()) {
                info = col.get(0);
                cache.put(adminOrgCode, info);
            }
        }
        if (info == null) {
            return LookupResult.fail("sHR系统中不存在编码为" + adminOrgCode + "的行政组织");
        }
        return LookupResult.ok(info);
    }

    /**
     * 根据编码查找岗位
     *
     * @param iPosition
     * @param positionCode
     * @param cache
     * @return
     * @throws BOSException
     */
    private LookupResult<PositionInfo> lookupPosition(
            IPosition iPosition,
            String positionCode,
            Map<String, PositionInfo> cache
    ) throws BOSException {
        PositionInfo info = cache.get(positionCode);
        if (info == null) {
            PositionCollection col = iPosition.getPositionCollection(
                    "where number = '" + escapeSql(positionCode) + "'");
            if (col != null && !col.isEmpty()) {
                info = col.get(0);
                cache.put(positionCode, info);
            }
        }
        if (info == null) {
            return LookupResult.fail("sHR系统中不存在编码为" + positionCode + "的岗位");
        }
        return LookupResult.ok(info);
    }

    /**
     * 根据编码查找考勤周期
     *
     * @param iPeriod
     * @param periodCode
     * @param cache
     * @return
     * @throws BOSException
     */
    private LookupResult<AttendancePeriodInfo> lookupPeriod(
            IAttendancePeriod iPeriod,
            String periodCode,
            Map<String, AttendancePeriodInfo> cache
    ) throws BOSException {
        AttendancePeriodInfo info = cache.get(periodCode);
        if (info == null) {
            AttendancePeriodCollection col = iPeriod.getAttendancePeriodCollection(
                    "where name = '" + escapeSql(periodCode) + "'");
            if (col != null && !col.isEmpty()) {
                info = col.get(0);
                cache.put(periodCode, info);
            }
        }
        if (info == null) {
            return LookupResult.fail("sHR系统中不存在名称为" + periodCode + "的考勤周期");
        }
        return LookupResult.ok(info);
    }

    /**
     * 构建考勤明细更新 SQL
     *
     * @param resultInfo
     * @param dynamicParams
     * @param projectMap
     * @return
     */
    private LookupResult<String> buildDailyUpdateSql(AttendanceResultInfo resultInfo,
                                                     Map<String, Object> dynamicParams,
                                                     Map<String, AttendanceProjectInfo> projectMap) {
        return buildUpdateSql("T_HR_ATS_AttendanceResult", resultInfo.getId().toString(),
                dynamicParams, projectMap);
    }

    /**
     * 构建考勤汇总更新 SQL
     *
     * @param resultInfo
     * @param dynamicParams
     * @param projectMap
     * @return
     */
    private LookupResult<String> buildMonthlyUpdateSql(AttendanceResultSummaryInfo resultInfo,
                                                       Map<String, Object> dynamicParams,
                                                       Map<String, AttendanceProjectInfo> projectMap) {
        return buildUpdateSql("T_HR_ATS_AttendanceResultSum", resultInfo.getId().toString(),
                dynamicParams, projectMap);
    }

    /**
     * 构建UPDATE SQL
     *
     * @param tableName
     * @param fid
     * @param dynamicParams
     * @param projectMap
     * @return
     */
    private LookupResult<String> buildUpdateSql(String tableName, String fid,
                                                Map<String, Object> dynamicParams,
                                                Map<String, AttendanceProjectInfo> projectMap) {
        StringBuilder sql = new StringBuilder("UPDATE ").append(tableName).append(" SET ");

        for (Map.Entry<String, Object> entry : dynamicParams.entrySet()) {
            String projectNumber = entry.getKey();
            Object projectValue = entry.getValue();

            String projectError = validateProjectEnabled(projectMap.get(projectNumber), projectNumber);
            if (projectError != null) {
                return LookupResult.fail(projectError);
            }
            sql.append("S").append(projectMap.get(projectNumber).getColumnNO())
                    .append("='").append(escapeSql(String.valueOf(projectValue))).append("', ");
        }
        sql.deleteCharAt(sql.lastIndexOf(","));
        sql.append(" WHERE FID = '").append(escapeSql(fid)).append("'");
        return LookupResult.ok(sql.toString());
    }

    // ==================== 校验 ====================

    /**
     * 校验考勤项目是否启用
     *
     * @param projectInfo
     * @param projectNumber
     * @return
     */
    private String validateProjectEnabled(AttendanceProjectInfo projectInfo, String projectNumber) {
        if (projectInfo == null) {
            return "考勤项目编码[" + projectNumber + "]不存在";
        }
        if (!BaseItemStateEnum.ENABLE.equals(projectInfo.getState())) {
            return "考勤项目[" + projectInfo.getName() + "] 编码[" + projectInfo.getNumber() + "]未启用";
        }
        return null;
    }

    // ==================== 工具方法 ====================

    /**
     * 构建失败标识符
     *
     * @param request
     * @return
     */
    private String buildFailIdentifier(AttendanceRequest request) {
        String dateOrPeriod = request.getAttendanceDate() != null
                ? request.getAttendanceDate()
                : request.getAttendancePeriod();
        return request.getPersonNumber() + "##" + dateOrPeriod + "##" + request.getAdminOrgCode() + "##" + request.getPositionCode();
    }

    private String escapeSql(String value) {
        if (value == null) {
            return "";
        }
        return value.replace("'", "''");
    }

    /**
     * 获取考勤项目列表
     *
     * @param ctx
     * @return
     * @throws BOSException
     */
    private Map<String, AttendanceProjectInfo> getAttendanceProjectMap(Context ctx) throws BOSException {
        try {
            IAttendanceProject iAttendanceProject = AttendanceProjectFactory.getLocalInstance(ctx);
            AttendanceProjectCollection attendanceProjectCol = iAttendanceProject.getAttendanceProjectCollection();
            Map<String, AttendanceProjectInfo> attendanceProjectMap = new HashMap<>();

            for (int i = 0; i < attendanceProjectCol.size(); i++) {
                AttendanceProjectInfo attendanceProjectInfo = attendanceProjectCol.get(i);
                attendanceProjectMap.put(attendanceProjectInfo.getNumber(), attendanceProjectInfo);
            }
            return attendanceProjectMap;
        } catch (BOSException e) {
            logger.error("获取考勤项目列表失败", e);
            throw new BOSException("获取考勤项目列表失败", e);
        }
    }

    // ==================== LookupResult 模式 ====================

    /**
     * 带错误信息的结果包装，避免用异常做流程控制
     */
    private static class LookupResult<T> {
        final T data;
        final String error;

        private LookupResult(T data, String error) {
            this.data = data;
            this.error = error;
        }

        static <T> LookupResult<T> ok(T data) {
            return new LookupResult<>(data, null);
        }

        static <T> LookupResult<T> fail(String error) {
            return new LookupResult<>(null, error);
        }
    }
}
