global without sharing class AssetMaintainAutoBatch implements Database.Batchable<Id>, Database.Stateful {
    global Integer allCount = 0;
    global Integer doneCount = 0;
    global List<String> errorList = new List<String>();
    global Date today = System.today();
    global String headerId;
    global Integer round;

    /**
    @description 调试用：传入盘点表头Id或遗失报告Id，得到需要维护的保有设备，无视批准情况
    @param headerId 盘点表头Id或遗失报告Id
    */
    global AssetMaintainAutoBatch(String headerId) {
        this.headerId = headerId;
        this.round = 4;
    }
    /**
    @description 5工作日前批准的盘点报告和遗失报告
    */
    global AssetMaintainAutoBatch() {
        this.headerId = '';
        this.round = 4;
    }
    global AssetMaintainAutoBatch(String headerId, Integer round){
        this.headerId = headerId;
        this.round = round;
    }

    global Iterable<Id> start(Database.BatchableContext bc) {
        List<Id> headerIdList = new List<Id>();
        List<Inventory_Header__c> invHeaderList = new List<Inventory_Header__c>();
        // 确认传入的Id是否有效，Id无效时返回空列表，execute不会执行
        if(String.isNotBlank(this.headerId)){
            invHeaderList = [
                SELECT Id
                FROM Inventory_Header__c
                WHERE Id=:this.headerId
            ];
            if(!invHeaderList.isEmpty()){
                headerIdList.add(this.headerId);
            }
            return headerIdList;
        }

        OlympusCalendar__c oc = [
            SELECT Id
                , Before_5_WorkDay__c
                , Before_4_WorkDay__c
            FROM OlympusCalendar__c
            WHERE Date__c =:today
        ];

        invHeaderList = [
            SELECT Id
            FROM Inventory_Header__c
            WHERE AutoMaintained__c = false
            AND Inventory_Header_approval_time__c <: oc.Before_4_WorkDay__c
        ];

        for(Inventory_Header__c header:invHeaderList){
            headerIdList.add(header.Id);
        }
        return headerIdList;
    }

    // schedule里scope设为1，保证是以表头为单位做修改
    // Database.executeBatch(new AssetMaintainAutoBatch(), 1);
    global void execute(Database.BatchableContext BC, List<Id> headerIdList) {
        allCount += headerIdList.size();
        Savepoint sp = Database.setSavepoint();
        try{
            AssetInfoInHeader info = loadAssetIdSet(headerIdList[0]);
            Map<Id, Integer> abandondLostMap = info.abandondLostMap;   //<AssetId, 丢失数>
            Map<Id, Integer> abandondDeficitMap = info.abandondDeficitMap; //<AssetId, 盘亏数>
            Map<Id, Integer> profitMap = info.profitMap;         //<AssetId, 盘盈数>
            Map<Id, Integer> consumedMap = info.consumedMap;       //<AssetId, 已消耗数>
            Set<Id> oneToOneAccessoryAssetIdSet = info.oneToOneAccessoryAssetIdSet; //一对一附属品Asset
            String yearMonthStart = info.yearMonthStart;
            String yearMonthEnd = info.yearMonthEnd;
            List<Asset> assetList = [
                SELECT Id
                   , Abandoned_Inventory__c
                   , Appended_Inventory_Frozen_Quantity__c
                   , Appended_Inventory_Profit_Quantity__c
                   , Asset_loaner_category__c
                   , Consumable_Guaranteen_end__c
                   , Consumed_Count__c
                   , Fixture_Model_No_F__c
                   , Fixture_OneToOne_Link__r.Main_Asset__c
                   , Internal_asset_location__c
                   , Internal_Asset_number_key__c
                   , Manage_Type__c
                   , Quantity
                   , Salesdepartment__c
                   , SerialNumber
                FROM Asset
                WHERE Id IN:abandondLostMap.keySet()
                 OR Id IN: abandondDeficitMap.keySet()
                 OR Id IN: profitMap.keySet()
                 OR Id IN: consumedMap.keySet()
            ];
            // 维护明细
            List<AssetMaintainDetail__c> amdLostList = new List<AssetMaintainDetail__c> (); // 废弃(丢失)
            List<AssetMaintainDetail__c> amdDeficitList = new List<AssetMaintainDetail__c> (); //废弃(盘亏)
            List<AssetMaintainDetail__c> amdProfitList = new List<AssetMaintainDetail__c> (); // 盘盈
            List<AssetMaintainDetail__c> amdConsumedList = new List<AssetMaintainDetail__c> (); // 耗材已消耗数

            List<Database.SaveResult> updResultList = Database.update(assetList, false);
            List<Asset> updateList = new List<Asset>();
            for(Integer i = 0; i < updResultList.size(); i++){
                Database.SaveResult sr = updResultList[i];
                if(!sr.isSuccess()){
                    for (Database.Error err : sr.getErrors()) {
                        errorList.add(err.getStatusCode() + ':' + err.getMessage() + err.getFields());
                    }
                    continue;
                }
                Asset ass = assetList[i];
                Boolean isOneToOneAccessory = oneToOneAccessoryAssetIdSet.contains(ass.Id);
                if(abandondLostMap.containsKey(ass.Id)){
                    AssetMaintainDetail__c amd = createAssetMaintainDetail(ass, isOneToOneAccessory);
                    amd.AbandonCount__c = abandondLostMap.get(ass.Id);
                    amdLostList.add(amd);

                    ass.Quantity = intValueOf(ass.Quantity) - abandondLostMap.get(ass.Id);
                    ass.ChangeQuantityReason__c = '废弃';
                    ass.Abandoned_Inventory__c = intValueOf(ass.Abandoned_Inventory__c) - abandondLostMap.get(ass.Id);
                    if(ass.Quantity <= 0) {
                        if(ass.Manage_Type__c == '个体管理'
                            || (ass.Consumable_Guaranteen_end__c != null && ass.Consumable_Guaranteen_end__c < today)){
                            ass.Status = FixtureUtil.assetStatusMap.get(FixtureUtil.AssetStatus.Fei_Qi.ordinal());
                        }
                    }
                }
                if(abandondDeficitMap.containsKey(ass.Id)){
                    if(String.isBlank(ass.Internal_Asset_number_key__c)){
                        AssetMaintainDetail__c amd = createAssetMaintainDetail(ass, isOneToOneAccessory);
                        amd.AbandonCount__c = abandondDeficitMap.get(ass.Id);
                        amdDeficitList.add(amd);

                        ass.Quantity = intValueOf(ass.Quantity) - abandondDeficitMap.get(ass.Id);
                        ass.Abandoned_Inventory__c = intValueOf(ass.Abandoned_Inventory__c) - abandondDeficitMap.get(ass.Id);
                        ass.ChangeQuantityReason__c = '盘亏';
                    }
                    if(ass.Quantity <= 0) {
                        if(ass.Manage_Type__c == '个体管理'
                            || (ass.Consumable_Guaranteen_end__c != null && ass.Consumable_Guaranteen_end__c < today)){
                            ass.Status =  FixtureUtil.assetStatusMap.get(FixtureUtil.AssetStatus.Fei_Qi.ordinal());
                        }
                    }
                }
                if(profitMap.containsKey(ass.Id)){
                    AssetMaintainDetail__c amd = createAssetMaintainDetail(ass, isOneToOneAccessory);
                    amd.MaintainCount__c = profitMap.get(ass.Id);
                    amdProfitList.add(amd);

                    ass.Quantity = intValueOf(ass.Quantity) + profitMap.get(ass.Id);
                    ass.Appended_Inventory_Profit_Quantity__c = intValueOf(ass.Appended_Inventory_Profit_Quantity__c) - profitMap.get(ass.Id);
                    ass.ChangeQuantityReason__c = '盘盈';
                }
                if(consumedMap.containsKey(ass.Id)){
                    AssetMaintainDetail__c amd = createAssetMaintainDetail(ass, isOneToOneAccessory);
                    amd.MaintainCount__c = consumedMap.get(ass.Id);
                    amdConsumedList.add(amd);

                    ass.Consumed_Count__c = intValueOf(ass.Consumed_Count__c) - consumedMap.get(ass.Id);
                }
                updateList.add(ass);
            }
            if(!updateList.isEmpty()){
                update updateList;
            }

            if(!amdLostList.isEmpty()){
                insertAssetMaintainDetails('废弃(丢失)', amdLostList, yearMonthStart, yearMonthEnd);
            }
            if(!amdDeficitList.isEmpty()){
                insertAssetMaintainDetails('废弃(盘亏)', amdDeficitList, yearMonthStart, yearMonthEnd);
            }
            if(!amdProfitList.isEmpty()){
                insertAssetMaintainDetails('盘盈(数量管理)', amdProfitList, yearMonthStart, yearMonthEnd);
            }
            if(!amdConsumedList.isEmpty()){
                insertAssetMaintainDetails('耗材已消耗数', amdConsumedList, yearMonthStart, yearMonthEnd);
            }
            updateHeaders(headerIdList[0]);

            doneCount += headerIdList.size();
        }
        catch (Exception e) {
            Database.rollback(sp);
            errorList.add(e.getMessage() + '\n' + e.getStackTraceString());
            System.debug(LoggingLevel.ERROR, e.getMessage() + '\n' + e.getStackTraceString());
            throw e;
        }
    }
    /**
    @description 盘点明细和遗失报告明细中取出保有设备
    @param headerId 盘点报告或遗失报告的Id
    @return 表头下保有设备的更新信息
    */
    private AssetInfoInHeader loadAssetIdSet(Id headerId){
        Map<Id, Integer> abandondLostMap = new Map<Id, Integer>();   //<AssetId, 丢失数>
        Map<Id, Integer> abandondDeficitMap = new Map<Id, Integer>(); //<AssetId, 盘亏数>
        Map<Id, Integer> profitMap = new Map<Id, Integer>();         //<AssetId, 盘盈数>
        Map<Id, Integer> consumedMap = new Map<Id, Integer>();       //<AssetId, 已消耗数>
        Set<Id> oneToOneAccessoryAssetIdSet = new Set<Id>(); //一对一附属品Asset
        String location = null;
        Datetime endDate = null;
        Id recordTypeId = null;

        List<Inventory_Detail__c> invdList = [SELECT Id
                                                , Asset__c
                                                , Abandoned_Inventory_Start__c
                                                , Inventory_Deviation__c
                                                , OneToOne_Accsessary__c
                                                , Inventory_Header__r.CreatedDate
                                                , Inventory_Header__r.Internal_asset_location__c
                                                , Inventory_Header__r.RecordTypeId
                                                , Inventory_Count__c
                                            FROM Inventory_Detail__c
                                            WHERE Inventory_Header__c =:headerId
                                            AND (Abandoned_Inventory_Start__c > 0
                                                    OR (Inventory_Count__c != null AND Inventory_Deviation__c != 0 )
                                                )
                                                ];
        List<Consum_Inventory_Detail__c> cinvdList = [SELECT Id
                                                         , Asset__c
                                                         , Abandoned_Inventory_Start__c
                                                         , Consumed_Count_Start__c
                                                         , Inventory_Header__r.CreatedDate
                                                         , Inventory_Header__r.Internal_asset_location__c
                                                         , Inventory_Header__r.RecordTypeId
                                                         , Inventory_Deviation__c
                                                         , Inventory_Count__c
                                                    FROM Consum_Inventory_Detail__c
                                                    WHERE Inventory_Header__c =:headerId
                                                    AND (Abandoned_Inventory_Start__c > 0
                                                        OR (Inventory_Count__c != null AND Inventory_Deviation__c != 0)
                                                        OR Consumed_Count_Start__c > 0
                                                        )
                                                    ];
        for(Inventory_Detail__c invd:invdList){
            endDate = invd.Inventory_Header__r.CreatedDate;
            location = invd.Inventory_Header__r.Internal_asset_location__c;
            recordTypeId = invd.Inventory_Header__r.RecordTypeId;
            if(invd.Abandoned_Inventory_Start__c > 0) {
                Integer c = intValueOf(invd.Abandoned_Inventory_Start__c);
                if(abandondLostMap.containsKey(invd.Asset__c)) {
                    c += abandondLostMap.get(invd.Asset__c);
                }
                abandondLostMap.put(invd.Asset__c, c);
            }
            if(invd.Inventory_Count__c != null && invd.Inventory_Deviation__c < 0){
                Integer c = -intValueOf(invd.Inventory_Deviation__c);
                if(abandondDeficitMap.containsKey(invd.Asset__c)) {
                    c += abandondDeficitMap.get(invd.Asset__c);
                }
                abandondDeficitMap.put(invd.Asset__c, c);
            }
            if(invd.Inventory_Count__c != null && invd.Inventory_Deviation__c > 0){
                Integer c = intValueOf(invd.Inventory_Deviation__c);
                if(profitMap.containsKey(invd.Asset__c)) {
                    c += profitMap.get(invd.Asset__c);
                }
                profitMap.put(invd.Asset__c, c);
            }
            if(invd.OneToOne_Accsessary__c){
                oneToOneAccessoryAssetIdSet.add(invd.Asset__c);
            }
        }
        for(Consum_Inventory_Detail__c cinvd:cinvdList){
            endDate = cinvd.Inventory_Header__r.CreatedDate;
            location = cinvd.Inventory_Header__r.Internal_asset_location__c;
            recordTypeId = cinvd.Inventory_Header__r.RecordTypeId;
            if(cinvd.Abandoned_Inventory_Start__c > 0){
                abandondLostMap.put(cinvd.Asset__c, intValueOf(cinvd.Abandoned_Inventory_Start__c));
            }
            if(cinvd.Inventory_Count__c != null && cinvd.Inventory_Deviation__c < 0){
                abandondDeficitMap.put(cinvd.Asset__c, -intValueOf(cinvd.Inventory_Deviation__c));
            }
            if(cinvd.Inventory_Count__c != null && cinvd.Inventory_Deviation__c > 0){
                profitMap.put(cinvd.Asset__c, intValueOf(cinvd.Inventory_Deviation__c));
            }
            if(cinvd.Consumed_Count_Start__c > 0){
                consumedMap.put(cinvd.Asset__c, intValueOf(cinvd.Consumed_Count_Start__c));
            }
        }
        // 获取同一存放地，同一记录类型的上一次盘点表头
        List<Inventory_Header__c> lastHeader = [
            SELECT CreatedDate
            FROM Inventory_Header__c
            WHERE CreatedDate <: endDate
            AND RecordTypeId =: recordTypeId
            AND Internal_asset_location__c =: location
            ORDER BY CreatedDate DESC
            LIMIT 1
        ];

        String yearMonthStart = '';
        String yearMonthEnd = '';
        if(lastHeader.size() == 1){
            yearMonthStart = getYearMonthStr(lastHeader[0].CreatedDate);
        }
        yearMonthEnd = getYearMonthStr(endDate);

        AssetInfoInHeader info = new AssetInfoInHeader();
        info.abandondLostMap = abandondLostMap;
        info.abandondDeficitMap = abandondDeficitMap;
        info.profitMap = profitMap;
        info.consumedMap = consumedMap;
        info.oneToOneAccessoryAssetIdSet = oneToOneAccessoryAssetIdSet;
        info.yearMonthStart = yearMonthStart;
        info.yearMonthEnd = yearMonthEnd;
        return info;
    }
    private Integer intValueOf(Decimal d) {
        if(d == null) {
            return 0;
        }
        return Integer.valueOf(d);
    }
    /**
    @description 根据asset造出维护明细
    @param ass 保有设备
    @param isOneToOneAccessory 是否是一对一附属品，来自盘点明细里的OneToOne_Accsessary__c字段
    @return 维护明细
    */
    private AssetMaintainDetail__c createAssetMaintainDetail(Asset ass, Boolean isOneToOneAccessory){
        AssetMaintainDetail__c amd = new AssetMaintainDetail__c();
        amd.Asset__c = ass.Id;
        amd.Internal_asset_location__c = ass.Internal_asset_location__c;
        amd.Salesdepartment__c = ass.Salesdepartment__c;
        amd.Fixture_Model_No__c = ass.Fixture_Model_No_F__c;
        amd.Internal_Asset_number_key__c = ass.Internal_Asset_number_key__c;
        amd.Manage_type__c = ass.Manage_type__c;
        amd.SerialNumber__c = ass.SerialNumber;
        if(isOneToOneAccessory) {
            amd.Is_OneToOne_Accessory__c = true;
            amd.OneToOne_Main__c = ass.Fixture_OneToOne_Link__r.Main_Asset__c;
        }
        return amd;
    }
    /**
    @description 造出维护表单和维护明细，并更新
    @param maintainType 维护类型
    @param amdList 维护表明细
    @param yearMonthStart 盘点开始日，来自盘点报告的Inventory_Date_From__c
    @param yearMonthEnd 盘点结束日，来自盘点报告的Inventory_Date_To__c
    */
    private void insertAssetMaintainDetails(String maintainType, List<AssetMaintainDetail__c> amdList,
                                            String yearMonthStart, String yearMonthEnd){

        // 插入缺少的维护单表头
        List<AssetMaintainHeader__c> amhList = [
            SELECT Id
                 , DetailCount__c
            FROM AssetMaintainHeader__c
            WHERE MaintainType__c =: maintainType
              AND Date__c =: today
              AND Status__c='已完成'
              LIMIT 1];
        Integer startIndex = 1;
        if(amhList.isEmpty()){
            AssetMaintainHeader__c newAmh = new AssetMaintainHeader__c (
                                          MaintainType__c = maintainType
                                        , Date__c = today
                                        , Status__c = '已完成');
            insert newAmh;
            amhList.add(newAmh);
        }
        else {
            startIndex = intValueOf(amhList[0].DetailCount__c);
        }

        // 插入明细
        String maintainReason = getMaintainReason(maintainType, yearMonthStart, yearMonthEnd);
        Datetime now = System.now();
        for (AssetMaintainDetail__c amd: amdList) {
            amd.AssetMaintainHeader__c = amhList[0].Id;
            amd.MaintainType__c = maintainType;
            amd.Batch_Status__c = '完成';
            if (maintainType == '废弃(丢失)' || maintainType == '废弃(盘亏)') {
                amd.AbandonReason__c = maintainReason;
                amd.AbandonedTime__c = now;
            }
            else {
                amd.MaintainReason__c = maintainReason;
                amd.MaintainedTime__c = now;
            }
            amd.OrderNumber__c = startIndex++;
        }

        insert amdList;
    }
    private void updateHeaders(Id headerId){
        List<Inventory_Header__c> ihList = [
            SELECT Id
            FROM Inventory_Header__c
            WHERE Id =:headerId
            FOR UPDATE
        ];
        List<Inventory_Header__c> updateList = new List <Inventory_Header__c>();
        for(Inventory_Header__c ih: ihList){
            ih.AutoMaintained__c = true;
            updateList.add(ih);
        }
        if(!updateList.isEmpty()){
            update updateList;
        }
    }
    /**
    @description 根据维护类型生成维护原因
    @param maintainType 维护类型
    @param yearMonthStart 盘点开始日，来自盘点报告的Inventory_Date_From__c
    @param yearMonthEnd 盘点结束日，来自盘点报告的Inventory_Date_To__c
    */
    private String getMaintainReason(String maintainType, String yearMonthStart, String yearMonthEnd){
        String reason = '';
        switch on maintainType{
            when '废弃(丢失)'{
                if(String.isNotBlank(yearMonthStart)){
                    reason += yearMonthStart + '盘点开始后至';
                }
                reason += yearMonthEnd + '盘点开始前的现场丢失数量';
            }
            when '废弃(盘亏)'{
                reason = yearMonthEnd + '盘点盘亏数量';
            }
            when '盘盈(数量管理)'{
                reason = yearMonthEnd + '盘点数量管理备品盘盈数量';
            }
            when '耗材已消耗数'{
                if(String.isNotBlank(yearMonthStart)){
                    reason += yearMonthStart + '盘点开始后至';
                }
                reason += yearMonthEnd + '盘点开始前的耗材消耗数量';
            }
        }
        return reason;
    }
    /**
    @description 日期转成年度月
    @param d 日期
    */
    private String getYearMonthStr(Datetime d){
        String res = '';
        if(d == null){
            return res;
        }
        if(d.month() <= 3){
            res += (d.year() - 1 - 1867);
        }
        else{
            res += (d.year() - 1867);
        }
        res += 'P' + d.month() + '月';
        return res;
    }

    global void finish(Database.BatchableContext BC) {
        this.round -= 1;
        // 当所有错误都是lock引起时才要重跑
        Boolean lockFailure = this.errorList.size() > 0;
        for(String errorMsg :this.errorList){
            if (!errorMsg.contains('Record Currently Unavailable')
                && !errorMsg.contains('记录当前不可用')
                && !errorMsg.contains('レコードは現在利用できません')) {
                lockFailure = false;
                break;
            }
        }
        if(lockFailure && this.round > 0){
            AssetMaintainAutoBatch bat = new AssetMaintainAutoBatch(this.headerId, this.round);
            Database.executeBatch(bat);
        }
        else{
            if (allCount != doneCount || this.errorList.size() > 0) {
                BatchEmailUtil be = new BatchEmailUtil();
                String[] toList = new String[]{};
                String title = '自动数据维护batch';
                String[] ccList = new String[]{};
                for (OrgWideEmailAddress tmpEmailObj : [SELECT Id, Address, DisplayName
                        FROM OrgWideEmailAddress
                        WHERE DisplayName like 'BatchNotify']) {
                    toList.add(tmpEmailObj.Address);
                }
                for (Consum_Apply_Meta__mdt camd : [SELECT Id
                            , Key__c
                            , ValueLong__c
                         FROM Consum_Apply_Meta__mdt
                        WHERE Package__c = 'AssetMaintainAutoBatch'
                          AND Key__c = 'ErrorMailAddress'
                        ORDER BY Key__c]) {

                    for (String email:camd.ValueLong__c.split(',')) {
                        if (!toList.contains(email)) {
                            toList.add(email);
                        }
                    }
                }
                be.failedMail(toList, ccList, title, String.join(this.errorList, '\n'), allCount, doneCount, errorList.size());
                be.send();
            }
        }
    }
    class AssetInfoInHeader {
        public Map<Id, Integer> abandondLostMap = new Map<Id, Integer>();   //<AssetId, 丢失数>
        public Map<Id, Integer> abandondDeficitMap = new Map<Id, Integer>(); //<AssetId, 盘亏数>
        public Map<Id, Integer> profitMap = new Map<Id, Integer>();         //<AssetId, 盘盈数>
        public Map<Id, Integer> consumedMap = new Map<Id, Integer>();       //<AssetId, 已消耗数>
        public Set<Id> oneToOneAccessoryAssetIdSet = new Set<Id>(); //一对一附属品Asset
        public String yearMonthStart;
        public String yearMonthEnd;
    }
}