/**********************************************************************************************************
 * Name         : FanDeleteBatch
 * Purpose      : Job that replaces DataCleanse and PcFanDelete jobs
 **********************************************************************************************************/

 public class FanDeleteBatch implements Database.Batchable<sObject>, Database.Stateful, Schedulable, Database.RaisesPlatformEvents, Database.AllowsCallouts {
    List<String> formResponsesCSV = new List<String>{
        'Fan Id,Form Response Id,FR Created Date,Form Id,Form Tool Name'
    };
    @TestVisible
    List<String> failedRecordsCSV = new List<String>{ 'SObject Type, Record ID' };
    @TestVisible
    Messaging.SingleEmailMessage email;

    Integer fansCount = 0;
    Integer deletedFansCount = 0;
    Integer deletedRecordsCount = 0;
    Integer daysToProcess;
    Boolean isPcFanDelete = false;

    public FanDeleteBatch(Boolean isPcFanDelete) {
        this.isPcFanDelete = isPcFanDelete;
        this.daysToProcess = 1;
    }

    public FanDeleteBatch(Boolean isPcFanDelete, Integer daysToProcess) {
        this.isPcFanDelete = isPcFanDelete;
        this.daysToProcess = daysToProcess > 99999 ? 99999 : daysToProcess;
    }

    public Database.QueryLocator start(Database.BatchableContext context) {
        if (isPcFanDelete) {
            String query =
                'SELECT Id, Email__c, Delete_My_Information__c, Delete_Reason__c, Delete_Request_Date__c, Customer_ID__c' +
                ' FROM Fan__c WHERE Delete_My_Information__c = true';
            return Database.getQueryLocator(query);
        } else {
            Integer age = ConfigUtils.getConfigInt(ConfigUtils.DATA_CLEANSE_AGE);
            return Database.getQueryLocator(
                'SELECT Id, Email__c, Customer_ID__c' +
                    ' FROM Fan__c WHERE Age__c < :age AND LastModifiedDate >= LAST_N_DAYS:' +
                    daysToProcess
            );
        }
    }

    public void execute(Database.BatchableContext context, List<Fan__c> fansToDelete) {
        fansCount += fansToDelete.size();
        Map<Id, String> mapFanIdFanEmail = new Map<Id, String>();
        List<Deleted_Record__c> deletedRecordsToInsert = new List<Deleted_Record__c>();

        for (Fan__c fan : fansToDelete) {
            mapFanIdFanEmail.put(fan.Id, fan.Email__c);
            Deleted_Record__c deletedRecord = new Deleted_Record__c();

            if (isPcFanDelete) {
                deletedRecord.Email__c = fan.Email__c;
                deletedRecord.Delete_Requested_Date__c = fan.Delete_Reason__c ==
                    UtilConstants.FAN_DELETE_REASON_FAN_REQUESTED
                    ? fan.Delete_Request_Date__c
                    : System.now();
                deletedRecord.Delete_Reason__c = String.isNotEmpty(fan.Delete_Reason__c)
                    ? fan.Delete_Reason__c
                    : UtilConstants.FAN_DELETE_REASON_MANUAL_DELETE;
            } else {
                deletedRecord.Delete_Requested_Date__c = System.now();
                deletedRecord.Delete_Reason__c = UtilConstants.FAN_DELETE_REASON_DATA_CLEANSE;
            }
            deletedRecord.Fan_ID__c = fan.Id;
            deletedRecord.Customer_ID__c = fan.Customer_ID__c;
            deletedRecord.Fan_Deleted_Date__c = System.now();
            deletedRecordsToInsert.add(deletedRecord);
        }

        List<Subscription__c> subscriptions = [
            SELECT Id
            FROM Subscription__c
            WHERE Fan_ID__c IN :mapFanIdFanEmail.keySet()
        ];

        List<Preference__c> preferences = [SELECT Id FROM Preference__c WHERE Fan_ID__c IN :mapFanIdFanEmail.keySet()];
        List<SubscriptionFanTriggerSendMapping__c> subFanTriggerSends = [
            SELECT Id
            FROM SubscriptionFanTriggerSendMapping__c
            WHERE Fan_ID__c IN :mapFanIdFanEmail.keySet()
        ];
        List<Subscription_History__c> subHistories = [
            SELECT Id
            FROM Subscription_History__c
            WHERE Fan_ID__c IN :mapFanIdFanEmail.keySet()
        ];
        List<Unique_customer__c> uniqueCustomers = [
            SELECT Id
            FROM Unique_customer__c
            WHERE Email__c IN :mapFanIdFanEmail.values()
        ];
        List<Ecommerce_Customer__c> eCustomers = [
            SELECT Id
            FROM Ecommerce_Customer__c
            WHERE Email__c IN :mapFanIdFanEmail.values()
        ];
        List<EcommerceOrder__c> eOrderLineItems = [
            SELECT Id
            FROM EcommerceOrder__c
            WHERE Email__c IN :mapFanIdFanEmail.values()
        ];
        List<Ecommerce_Order__c> eOrders = [
            SELECT Id
            FROM Ecommerce_Order__c
            WHERE Email__c IN :mapFanIdFanEmail.values()
        ];
        List<Held_Email__c> heldEmails = [SELECT Id FROM Held_Email__c WHERE Email__c IN :mapFanIdFanEmail.values()];
        List<Fan_Retarget__c> fanRetargets = [
            SELECT Id
            FROM Fan_Retarget__c
            WHERE Fan_ID__c IN :mapFanIdFanEmail.keySet()
        ];

        List<Form_Response__c> formResponses = new List<Form_Response__c>();
        List<PC_DeleteRequest__c> pcDeleteRequestsToInsert;
        if (ConfigUtils.getConfigBoolean(ConfigUtils.NODEJS_ACTIVE)) {
            List<SnowflakeFormResponseWrapper> formResponseWrappers = NodeJsHelper.performFormResponsesQuery(
                new List<Id>(mapFanIdFanEmail.keySet())
            );
            pcDeleteRequestsToInsert = FanDeleteUtils.createPcDeleteRecords(formResponseWrappers);
            for (SnowflakeFormResponseWrapper fr : formResponseWrappers) {
                formResponsesCSV.add(
                    fr.fanId + ',' + fr.frId + ',' + fr.createdDate + ',' + fr.formId + ',' + fr.formToolName
                );
            }
            formResponses = [
                SELECT
                    CreatedDate,
                    Email__c,
                    Fan_ID__c,
                    Fan_ID__r.Email__c,
                    Form_ID__c,
                    Form_ID__r.Form_Tool_Name__c,
                    Id
                FROM Form_Response__c
                WHERE Fan_ID__c = :mapFanIdFanEmail.keySet()
            ];
        } else {
            pcDeleteRequestsToInsert = FanDeleteUtils.createPcDeleteRecords(mapFanIdFanEmail, formResponses);
            for (Form_Response__c fr : formResponses) {
                formResponsesCSV.add(
                    fr.Fan_Id__c +
                        ',' +
                        fr.Id +
                        ',' +
                        fr.CreatedDate +
                        ',' +
                        fr.Form_ID__c +
                        ',' +
                        fr.Form_ID__r.Form_Tool_Name__c
                );
            }
        }

        Database.insert(deletedRecordsToInsert, false);
        Database.insert(pcDeleteRequestsToInsert, false);

        Database.SaveResult[] flagRecordsForDeletionResults = DmlUtils.flagRecordsForDeletion(subHistories);
        flagRecordsForDeletionResults.addAll(DmlUtils.flagRecordsForDeletion(formResponses));

        Database.DeleteResult[] deleteResults = Database.delete(fanRetargets, false);
        deleteResults.addAll(Database.delete(preferences, false));
        deleteResults.addAll(Database.delete(subFanTriggerSends, false));
        deleteResults.addAll(Database.delete(subscriptions, false));
        deleteResults.addAll(Database.delete(fansToDelete, false));
        deleteResults.addAll(Database.delete(heldEmails, false));
        deleteResults.addAll(Database.delete(eCustomers, false));
        deleteResults.addAll(Database.delete(eOrders, false));
        deleteResults.addAll(Database.delete(eOrderLineItems, false));
        deleteResults.addAll(Database.delete(uniqueCustomers, false));

        for (Database.DeleteResult deleteResult : deleteResults) {
            if (!deleteResult.isSuccess()) {
                failedRecordsCSV.add(
                    deleteResult.getId() + ',' + deleteResult.getId().getSObjectType().getDescribe().getName()
                );
            } else {
                if (deleteResult.getId().getSObjectType() == Fan__c.getSObjectType()) {
                    deletedFansCount++;
                } else {
                    deletedRecordsCount++;
                }
            }
        }
        for (Database.SaveResult deleteResult : flagRecordsForDeletionResults) {
            if (!deleteResult.isSuccess()) {
                failedRecordsCSV.add(
                    deleteResult.getId() + ',' + deleteResult.getId().getSObjectType().getDescribe().getName()
                );
            } else {
                deletedRecordsCount++;
            }
        }
    }

    public void finish(Database.BatchableContext context) {
        Integer failedRecordsCount = failedRecordsCSV.size() - 1;
        Integer formResponsesCount = formResponsesCSV.size() - 1;
        List<String> body = new List<String>();
        body.add('- Fans found: ' + fansCount.format());
        body.add('- Fans deleted: ' + deletedFansCount.format());
        body.add('- Records deleted: ' + deletedRecordsCount.format());
        body.add('- Records failed: ' + failedRecordsCount.format());

        email = new Messaging.SingleEmailMessage();

        if (isPcFanDelete) {
            email.setToAddresses(ConfigUtils.getConfigMultipleStrings(ConfigUtils.SET_SUPPORT_EMAIL));
            email.setSubject('PcFanDelete ' + Datetime.now().format());
        } else {
            email.setToAddresses(ConfigUtils.getConfigMultipleStrings(ConfigUtils.DATA_CLEANSE_RECIPIENTS));
            email.setSenderDisplayName(ConfigUtils.getConfigString(ConfigUtils.DATA_CLEANSE_SENDER));
            email.setSubject('Data Cleanse ' + Datetime.now().format());
        }

        List<Messaging.EmailFileAttachment> attachments = new List<Messaging.EmailFileAttachment>();
        String currentDate = String.valueOf(System.today()).split(' ')[0];

        if (failedRecordsCount > 0) {
            Messaging.EmailFileAttachment failedAttachment = new Messaging.EmailFileAttachment();
            Blob csvBlob = Blob.valueOf(String.join(failedRecordsCSV, '\n'));
            failedAttachment.setFileName('Failed_' + currentDate + '.csv');
            failedAttachment.setBody(csvBlob);
            attachments.add(failedAttachment);
        }
        if (formResponsesCount > 0) {
            Messaging.EmailFileAttachment formResponsesAttachment = new Messaging.EmailFileAttachment();
            Blob csvBlob = Blob.valueOf(String.join(formResponsesCSV, '\n'));
            formResponsesAttachment.setFileName('FormResponses_' + currentDate + '.csv');
            formResponsesAttachment.setBody(csvBlob);
            attachments.add(formResponsesAttachment);
        }

        if (!attachments.isEmpty()) {
            email.setFileAttachments(attachments);
        }

        email.setPlainTextBody(String.join(body, '\n'));
        Messaging.sendEmail(new List<Messaging.SingleEmailMessage>{ email });
    }

    public void execute(SchedulableContext sc) {
        Database.executeBatch(
            new FanDeleteBatch(isPcFanDelete),
            ConfigUtils.getConfigInt(ConfigUtils.PCFANDELETE_BATCH_SIZE)
        );
    }

    public static void scheduleDataCleanse() {
        BatchJobUtils.scheduleJob(
            new FanDeleteBatch(false),
            ConfigUtils.getConfigString(ConfigUtils.DATA_CLEANSE_CRON_EXP),
            'DataCleanse'
        );
    }

    public static void schedulePcFanDelete() {
        BatchJobUtils.scheduleJob(
            new FanDeleteBatch(true),
            ConfigUtils.getConfigString(ConfigUtils.PCFANDELETE_CRON_EXP),
            'PcFanDelete'
        );
    }
}
