<?php

namespace App\Http\Controllers\Grade;


use App\ABFL\Services\Services;
use App\ApprovalRule;
use App\ApprovalRuleLog;
use App\ApprovalStatusLog;
use App\ApprovedPincodeLists;
use App\AutoReject;
use App\AutoRejectRule;
use App\AutoRejectLogs;
use App\Avanse\Processes\AvanseProcesses;
use App\Avanse\Services\AvanseServices;
use App\ConsumerApplication;
use App\ConsumerApplicationElement;
use App\ConsumerApplicationFiles;
use App\ConsumerApplicationGrade;
use App\CreditReport;
use App\DecisionTable;
use App\DocumentRule;
use App\DocumentRuleLog;
use App\DocumentSets;
use App\EPFUANLookup;
use App\Events\V1\EventTableOfOffer;
use App\FOIR;
use App\GradeLock;
use App\Grades;
use App\GradesV1;
use App\GSTPANData;
use App\Http\Controllers\Admin\ConsumerApplicationController;
use App\Http\Controllers\Admin\EMICalculation;
use App\Http\Controllers\ApiController;
use App\Http\Controllers\Controller;
use App\Imports\TestImport;
use App\LendBox;
use App\MerchantOffers;
use App\PreApproval;
use App\PreApprovalLog;
use App\Products;
use App\PWAApplicationForm;
use App\ScoreCard;
use App\Slack;
use App\Comments;
use App\StatusLogs;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
use App\PreApprovalLock;
use App\AutoRejectLock;
use App\DocumentRuleLock;
use App\ProductOffers;
use App\Events\V1\EventBreApiCall;
use App\EpfFailureLog;
use Illuminate\Support\Facades\Storage;
use App\Logging\CloudWatchLoggerFactory;
use App\BreLog;
use App\JsonBreApprovalStatusLog;
use App\JWTAccess;
use Hashids\Hashids;
use App\Http\Controllers\JsonBREController;
use App\Http\Controllers\Grade\AutoRejection;
use App\LoanDecisionApprovedAmount;
use App\Http\Controllers\Admin\autoBRE;


/**
 * TableOfOffersTrait holds the Table-of-Offers (TOF) decisioning flow. It is
 * used by GradeController so the flow runs on the same instance the BRE already
 * populated (policyPassed, emiData, Acf_foir, Afoir, Atenure,
 * bureauSurrogateLimits, tofFoirLimit, appId, ...) and can reuse the BRE helpers
 * (saveApprovalStatus, setFOIRAttributes, getFOIRCalculation,
 * addNegativeResidualLog, addComment, ...) declared on GradeController.
 *
 * Keeping this in a trait avoids copying state between two objects: the BRE
 * (producer) and the TOF flow (consumer) share a single $this.
 */
trait TableOfOffersTrait
{
    /**
     * TOF-only state. These properties are used exclusively by the Table-of-Offers
     * flow, so they are declared on the trait rather than on GradeController.
     *
     * The shared input state the flow consumes (policyPassed, emiData, Acf_foir,
     * Afoir, Atenure, bureauSurrogateLimits, powerSavingPolicyPassed,
     * bankingPolicyPassed, bureauSurrogatePolicyPassed, tofFoirLimit,
     * tofCfFoirLimit, appId, ...) is declared on GradeController and populated by
     * its BRE flow before the TOF flow runs on the same instance.
     */
    public $eligibleTenureOffer = null;
    public $eligibleTenure = 0;
    public $powerSavingEMI;
    public $TOFApprovedEligibility = [];
    public $bureauSurrogateEligibility = [];
    public $powerSavingEligibility = [];
    public $bankingSurrogateEligibility = [];
    public $bureauSurrogatePolicyPassed = [];


    public function generateApprovalLog($app_id,$reason,$req_id=''){
        if (_env('JSON_BRE_ACTIVE')) {
            JsonBreApprovalStatusLog::insert([
                'rule_id' => 0,
                'application_id' => $app_id,
                'failed_condition' => null,
                'reason' => $reason,
                'request_batch_id' => $req_id,
                'status' => 1,
                'created_at' => Carbon::now()->toDateTimeString(),
                'updated_at' => Carbon::now()->toDateTimeString(),
            ]);
        } else {
            ApprovalStatusLog::insert([
                'rule_id' => 0,
                'application_id' => $app_id,
                'failed_condition' => null,
                'reason' => $reason,
                'request_batch_id' => $req_id,
                'status' => 1,
                'created_at' => Carbon::now()->toDateTimeString(),
                'updated_at' => Carbon::now()->toDateTimeString(),
            ]);
        }
    }

    /**
     * Entry point for the JSON-BRE Table-of-Offers initiation. Extracts the TOF
     * limits (tenure/foir/cf_foir) from the passed grade rules, sets the TOF
     * attributes, runs the decision flow, fires the webhook and returns the
     * decision-table result. Invoked directly by GradeController's BRE driver on
     * the same instance.
     */
    public function initiateTableOfOffers($application, $status, $req_id, $application_id, $filteredGradeRules)
    {
        // Extract values from filtered grade rules
        $tenureValues = [];
        $foirValues = [];
        $cfFoirValues = [];

        foreach ($filteredGradeRules as $rule) {
            $ruleDetails = json_decode($rule->rules, true);
            if (isset($ruleDetails['max_net_tenure'])) {
                $tenureValues[] = (int)$ruleDetails['max_net_tenure'];
            }
            if (isset($ruleDetails['foir'])) {
                $foirValues[] = (int)$ruleDetails['foir'];
            }
            if (isset($ruleDetails['cf_foir'])) {
                $cfFoirValues[] = (int)$ruleDetails['cf_foir'];
            }
        }

        $cwFactory = new CloudWatchLoggerFactory();
        // values hard coded if no policy passed as requested by credit team
        $this->setTOFAttributes($application, !empty($tenureValues) ? max($tenureValues) : 60, !empty($foirValues) ? max($foirValues) : 75, !empty($cfFoirValues) ? max($cfFoirValues) : 50);
        // Call saveDecisionInTable if all rules fail including the power saving rule
        $cwFactory->logger(json_encode(['title' => 'AutoDecision: SavetoDecisionTable', 'application_id' => $application_id]));
        $decisionTableResult = $this->saveDecisionInTable($application, $status, true, $req_id);
        $this->triggerTableOfOffersWebhook($application_id);
        $cwFactory->logger(json_encode(['title' => 'Auto Decision: BRE execution complete', 'application_id' => $application_id, 'decisionTableResult' => $decisionTableResult]));
        return $decisionTableResult;
    }

    public function saveDecisionInTable($applicationDetails, $approved, $isAdminCall = false, $req_id = '')
    {
        $approvalStatus = false;
        if (!$approved && !$isAdminCall) {
            return $approved;
        }

        try {
            if (!empty($this->policyPassed)  || !empty($this->bureauSurrogatePolicyPassed)) {
                DecisionTable::where('application_id', $this->appId)->delete();
                LoanDecisionApprovedAmount::where('application_id', $this->appId)->delete();

                $table = $this->buildDecisionTable();
                // Pass $offers by reference to handleRegularAdminCall so it can be modified (e.g., adding 'eligible_amount')
                $offers = $this->processOffers($table);

                if ($isAdminCall) {
                    $approvalStatus = $this->handleRegularAdminCall($approved, $offers, $req_id);
                }
            }
            if (!$isAdminCall) { // Solar form/ No touch flow
                $approvalStatus = $this->handleSolarFormCall($offers);
            }

            // Power saving and banking BRE
            $approvalStatus = $this->runTOFwithCustomEMI($applicationDetails);
            // set the best offer on the application
            $approvalStatus = $this->setFinalOffer($applicationDetails);
            return $approvalStatus;

        } catch (\Exception $e) {
            $this->handleException($e);
            return false;
        }
    }

    private function buildDecisionTable()
    {
        $table = [];

        foreach ($this->Acf_foir as $cfF) {
            $table[$cfF['offer_id']][] = [
                'type' => 'cf_foir',
                'value' => $cfF['cfFoir'],
                'status' => $cfF['status']
            ];
        }

        foreach ($this->Afoir as $cfF) {
            $table[$cfF['offer_id']][] = [
                'type' => 'foir',
                'value' => $cfF['foirIncludingCFEMI'],
                'status' => $cfF['status']
            ];
        }

        foreach ($this->Atenure as $cfT) {
            $table[$cfT['offer_id']][] = [
                'type' => 'tenure',
                'value' => $cfT['tenure'],
                'status' => $cfT['status']
            ];
        }

        return $table;
    }

    private function processOffers(array $table)
    {
        $offers = [];
        $date = Carbon::now()->toDateTimeString();

        foreach ($table as $offerId => $items) {
            $offerData = $this->processOfferItems($items);

            if (!isset($this->emiData[$offerId])) {
                $offerData['reason'] .= 'Emi data missing';
                $offerData['status'] = 1;
                continue;
            }

            $offers[] = $this->buildOfferRecord($offerId, $offerData, $date);
        }

        return $offers;
    }

    private function processOfferItems(array $items)
    {
        $cf_foir = 0;
        $foir = 0;
        $tenure = 0;
        $status = 0;
        $reason = '';

        foreach ($items as $item) {
            switch ($item['type']) {
                case 'cf_foir':
                    $cf_foir = $item['value'];
                    break;
                case 'foir':
                    $foir = $item['value'];
                    break;
                case 'tenure':
                    $tenure = $item['value'];
                    break;
            }

            if (!$status) {
                $status = $item['status'];
            }

            if ($item['status']) {
                $reason .= $item['type'] . ',';
            }

            if (is_string($tenure) && $tenure == 'no offer found') {
                $reason .= 'offer missing,';
                $status = 1;
            }
        }

        return [
            'cf_foir' => $cf_foir,
            'foir' => $foir,
            'tenure' => $tenure,
            'status' => $status,
            'reason' => rtrim($reason, ',')
        ];
    }

    private function buildOfferRecord(string $offerId, array $offerData, string $date)
    {
        return [
            'application_id' => $this->appId,
            'offer_id' => $offerId,
            'cf_foir' => $offerData['cf_foir'],
            'foir' => $offerData['foir'],
            'tenure' => $offerData['tenure'],
            'status' => $offerData['status'],
            'payable' => $this->emiData[$offerId]['payable'],
            'emi' => $this->emiData[$offerId]['emi'],
            'interest_rate' => $this->emiData[$offerId]['interest_rate'],
            'processing_fee' => $this->emiData[$offerId]['processing_fee'],
            'up_front_from_customer' => $this->emiData[$offerId]['upfront_from_customer'],
            'bureau_surrogate_limit_emi' => null,
            'bureau_surrogate_limit_amount' => null,
            'reason' => $offerData['reason'],
            'updated_at' => $date,
            'created_at' => $date
        ];
    }

    private function handleRegularAdminCall($approved, array &$offers, $req_id)
    {
        $application = $this->getApplicationWithRelations();
        $this->removeRejectedOffers($offers);

        if (empty($this->emiData)) { // residual EMI flow
            return $this->handleEmptyEmiData($application, $offers, $approved, $req_id);
        }

        $this->updateMinimumTenureOffersWithEligibleAmountAndEmi($application, $offers);
        $offers = $this->filterValidMinimumTenureOffers($application, $offers);
        if (empty($offers)) {
            return false;
        }

        $this->TOFApprovedEligibility['approved_amount'] = $application->approved_amount;
        $final_offer_id = $this->selectEligibleTenureOffer($application, $offers);
        $this->TOFApprovedEligibility['emi'] = $this->emiData[$final_offer_id]['emi'];
        $this->TOFApprovedEligibility['offer_id'] = $final_offer_id;
        $this->TOFApprovedEligibility['net_loan_amount'] = $this->emiData[$final_offer_id]['net_loan_amount'];
        $this->TOFApprovedEligibility['loan_amount'] = $this->emiData[$final_offer_id]['loan_amount'];
        $this->TOFApprovedEligibility['table_of_offer_logic'] = 'minimum_tenure';
        $this->TOFApprovedEligibility['req_id'] = $req_id;

        // bureau surrogate limit & minimum tenure comparison
        if (!empty($this->bureauSurrogateLimits)) {
            $highestBureauSurrogate = max($this->bureauSurrogateLimits);
            $this->bureauSurrogateEligibility['table_of_offer_logic'] = 'bureau_surrogate_limit';
            if ($highestBureauSurrogate > $this->TOFApprovedEligibility['net_loan_amount']) {
                $filteredLimits = [['emi' => $this->emiData[$final_offer_id]['emi'], 'loan_amount' => $application->approved_amount, 'id' => $final_offer_id]];
            } else {
                $filteredLimits = $this->filterBureauSurrogateByFOIR($application, $highestBureauSurrogate);
            }
            if (!empty($filteredLimits)) {
                $this->updateOffersWithBureauSurrogateLimit($offers, $filteredLimits);
            }
            $bureau_surrogate_offer_data = array_values(array_filter($filteredLimits, function ($item) use ($final_offer_id, $highestBureauSurrogate) {
                return isset($item['id']) && $item['id'] == $final_offer_id;
            }));
            $this->bureauSurrogateEligibility['approved_amount'] = ($highestBureauSurrogate < $this->TOFApprovedEligibility['net_loan_amount']) ? $bureau_surrogate_offer_data[0]['loan_amount'] : $this->TOFApprovedEligibility['loan_amount'];
            $this->bureauSurrogateEligibility['emi'] = ($highestBureauSurrogate < $this->TOFApprovedEligibility['net_loan_amount']) ? $bureau_surrogate_offer_data[0]['emi'] : $this->emiData[$final_offer_id]['emi'];
            $this->bureauSurrogateEligibility['offer_id'] = $final_offer_id;
            $this->bureauSurrogateEligibility['req_id'] = $req_id;
        }
        if (!empty($offers)) {
            DecisionTable::insert($offers);
        }

        return $approved ? true : false;
    }

    public function handleSolarFormCall($offers) {
        $approvalStatus = $this->checkApprovalStatus($offers);
        if (!empty($offers)) {
            DecisionTable::insert($offers);
        }
        $this->handleRejection($approvalStatus, false);
        return $approvalStatus;
    }

    private function getApplicationWithRelations()
    {
        return ConsumerApplication::with('merchant', 'application_element', 'emi', 'activeOffer')
            ->where('id', $this->appId)
            ->first();
    }

    private function removeRejectedOffers(array $offers)
    {
        foreach ($this->emiData as $offerId => $emiDetails) {
            if ($this->isOfferRejected($offerId, $offers)) {
                unset($this->emiData[$offerId]);
            }
        }
    }

    private function isOfferRejected($offerId, array $offers)
    {
        foreach ($offers as $offer) {
            if ($offer['offer_id'] == $offerId && $offer['status'] == 1) {
                return true;
            }
        }
        return false;
    }

    private function selectEligibleTenureOffer($application, array $offers)
    {
        $this->getEligibleTenureOffer($application);

        if ($this->eligibleTenureOffer !== null) {
            $eligibleOfferData = collect($offers)->firstWhere('offer_id', $this->eligibleTenureOffer);
            return $eligibleOfferData ? $eligibleOfferData['offer_id'] : $offers[0]['offer_id'];
        }

        return $offers[0]['offer_id'];
    }

    private function processOfferChange($application, $final_offer_id, $req_id, $source, $tofType=null)
    {
        $previous_applied_offer = $application->offer_id ?? 0;

        $this->regenerateEMIAfterDecision(
            $application,
            $final_offer_id,
            $req_id,
            $source,
            $tofType
        );

        ConsumerApplicationElement::where('application_id', $application->id)
            ->whereNull('previous_applied_offer')
            ->update(['previous_applied_offer' => $previous_applied_offer]);
    }

    private function addCommentCustomFlow($application, $tofType){
        $user = Auth::user();
        $username = $user->name ?? null;

        $comment = "Applied Amount - Rs. " . $application->loan_amount . " <br>
                Eligible Amount - Rs. " . $application->approved_amount . " <br>
                EMI - Rs. " . $application->emi->residual_emi . " <br>
                Changed as per - " . $tofType . " <br>";

        if (!empty($username)) {
            $comment .= "@" . $username . " Please reach out to the @credit team if any additional documents are required.";
        } else {
            $comment .= "Please reach out to the credit team if any additional documents are required.";
        }

        $this->addComment($this->appId, $user->id ?? 107, $comment, 1, true);
    }

    private function updateApplicationLogic($application, $logic, $residual_emi=null, $approvedAmount=null)
    {
        if ($application->emi) {
            if ($logic == 'residual_emi' && $residual_emi) {
                $application->emi->residual_emi = $residual_emi;
                $application->approved_amount = $approvedAmount;
                $application->save();
            } elseif ($logic == 'bureau_surrogate_limit' && $residual_emi) {
                $application->emi->residual_emi = $residual_emi;
                $application->emi->bureau_surrogate_amount = $approvedAmount;
                $application->approved_amount = $approvedAmount;
                $application->save();
            }
        }
        $application->emi->table_of_offers_logic = $logic;
        $application->emi->save();
    }

    private function handleEmptyEmiData($application, array $offers, $approved, $req_id, $emiAmount=0, $type=null, $skipOfferChange=false)
    {
        if ($emiAmount && $type) {
            $returnValue = $this->handleEmptyEmiDataWithAmount($application, $offers, $approved, $req_id, $emiAmount, $type, $skipOfferChange);
            if (is_numeric($returnValue) && $returnValue == 0){
                return false;
            }
        } else {
            $emiDetails = $this->getNextBasedAmountOffer($application, true);
            if (empty($emiDetails)) {
                DecisionTable::insert($offers);
                return false;
            }
            $result = $this->processResidualEmi($application, $offers, $emiDetails, $approved, $req_id, $emiAmount, $type);
            if (is_numeric($result) && $result == 0) {
                return false;
            }
            DecisionTable::insert($offers);
        }
        return $approved;
    }

    public function handleEmptyEmiDataWithAmount($application, array $offers, $approved, $req_id, $emiAmount, $type, $skipOfferChange=false) {
        $emiDetails = $this->getNextBestAmountOfferWithCustomEMI($application, $emiAmount);
        $result = $this->processResidualEmi($application, $offers, $emiDetails, $approved, $req_id, $emiAmount, $type, $skipOfferChange);
        return $result;
    }

    private function processResidualEmi($application, array &$offers, $emiDetails, $approved, $req_id, $emi, $tofType, $skipOfferChange=false)
    {
        $closestAmount = null;
        if ($emi && !$emiDetails) {
            return 0;
        }
        $table_of_offer_logic = 'residual_emi';
        $offerIds = array_column($offers, 'offer_id');
        $filteredEmiDetails = array_filter($emiDetails, function ($emiDetail) use ($offerIds) {
            return in_array($emiDetail->id, $offerIds);
        });

        $offerId = $application->offer_id ?? null;
        if (!empty($offerId)) {
            $loan_amount = $application->loan_amount ?? 0 ;
            $net_loan_amount = (new autoBRE())->calculateNetLoanAmount($loan_amount, $application->activeOffer);
        }

        // $this->updateOffersWithEligibleAmount($offers, $filteredEmiDetails);
        if ($emi) {
            $filteredEmiDetails = $this->validateEMIOffers($filteredEmiDetails, $application);
            if (empty($filteredEmiDetails)) {
                if ($tofType == 'POWER-SAVING'){
                    $this->addNegativeResidualLog($application->id, collect($this->powerSavingPolicyPassed)->pluck('id')->toArray(), 'Eligible net loan amount is not within 50k and 5L limit');
                } else if ($tofType == 'BANKING-ABB') {
                    $this->addNegativeResidualLog($application->id, collect($this->bankingPolicyPassed)->pluck('id')->toArray(), 'Eligible net loan amount is not within 50k and 5L limit');
                }
                return 0;
            }
            $calculatedLoanAmount = $this->saveToLoanDecisionApprovedAmount($application->id, $tofType, $filteredEmiDetails);
            if ($tofType == 'POWER-SAVING') {
                $this->powerSavingEligibility['approved_amount'] = $calculatedLoanAmount->calculated_approved_amount;
                $this->powerSavingEligibility['emi'] = $calculatedLoanAmount->emi_amount;
                $this->powerSavingEligibility['offer_id'] = $calculatedLoanAmount->offer_id;
                $this->powerSavingEligibility['req_id'] = $req_id;
            } else if ($tofType == 'BANKING-ABB') {
                $this->bankingSurrogateEligibility['approved_amount'] = $calculatedLoanAmount->calculated_approved_amount;
                $this->bankingSurrogateEligibility['emi'] = $calculatedLoanAmount->emi_amount;
                $this->bankingSurrogateEligibility['offer_id'] = $calculatedLoanAmount->offer_id;
                $this->bankingSurrogateEligibility['req_id'] = $req_id;
            }
        } else {
            $filteredEmiDetails = $this->filterValidEmiDetailsForTableOfOffers($filteredEmiDetails, $application);
            if (empty($filteredEmiDetails)) {
                return 0;
            }

            $this->updateOffersWithEligibleAmount($offers, $filteredEmiDetails);
            $offers = $this->filterDecisionTableOffersByEligibleAmount($application, $offers);
            $highestLoanOffer = collect($filteredEmiDetails)->sortByDesc('loan_amount')->first();
            $highestLoanOfferId = $highestLoanOffer->id ?? null;
            $this->TOFApprovedEligibility['approved_amount'] = $highestLoanOffer->loan_amount;
            $this->TOFApprovedEligibility['net_loan_amount'] = $highestLoanOffer->net_loan_amount;
            $this->TOFApprovedEligibility['loan_amount'] = $highestLoanOffer->loan_amount;
            $this->TOFApprovedEligibility['emi'] = $highestLoanOffer->emi;
            $this->TOFApprovedEligibility['offer_id'] = $highestLoanOfferId;
            $this->TOFApprovedEligibility['req_id'] = $req_id;

            if (!empty($this->bureauSurrogateLimits)) {
                $this->bureauSurrogateEligibility['table_of_offer_logic'] = 'bureau_surrogate_limit';
                $highestBureauSurrogate = max($this->bureauSurrogateLimits);
                if ($highestBureauSurrogate > $this->TOFApprovedEligibility['net_loan_amount']) {
                    $filteredLimits = [['emi' => $this->emiData[$highestLoanOfferId]['emi'], 'loan_amount' => $this->TOFApprovedEligibility['net_loan_amount'], 'id' => $highestLoanOfferId]];
                } else {
                    $filteredLimits = $this->filterBureauSurrogateByFOIR($application, $highestBureauSurrogate);
                }
                // filter bureau surrogate eligible limits by foir, cffoir
                // $filteredLimits = $this->filterBureauSurrogateByFOIR($application);
                if (!empty($filteredLimits)) {
                    $this->updateOffersWithBureauSurrogateLimit($offers, $filteredLimits);
                    $this->bureauSurrogateEligibility['req_id'] = $req_id;
                    if ($highestBureauSurrogate < $this->TOFApprovedEligibility['net_loan_amount']) {
                        foreach ($filteredEmiDetails as $item) {
                            $loanAmount = $item->loan_amount ?? null;
                            if ($loanAmount === null || $loanAmount <= $this->bureauSurrogateEligibility['approved_amount']) {
                                continue;
                            }
                            if ($closestAmount === null || $loanAmount < $closestAmount) {
                                $closestAmount = $loanAmount;
                                $closestItem = $item;
                            }
                        }
                        $this->bureauSurrogateEligibility['offer_id'] = $closestItem->id;
                        $this->bureauSurrogateEligibility['emi'] = $closestItem->emi;
                        $this->bureauSurrogateEligibility['approved_amount'] = $closestItem->loan_amount;
                    } else {
                        $this->bureauSurrogateEligibility['approved_amount'] = $this->TOFApprovedEligibility['approved_amount'];
                        $this->bureauSurrogateEligibility['emi'] = $highestLoanOffer->emi;
                        $this->bureauSurrogateEligibility['offer_id'] = $highestLoanOfferId;
                    }
                }
            }
        }

        return true;
    }

    function validateEMIOffers(array $offers, $application): ?array
    {
        $validOffers = array_filter($offers, function ($value) use ($application){
            return $this->isValidTableOfOffersAmount($value->loan_amount ?? null, $application);
        });
        return $validOffers;
    }

    private function filterValidEmiDetailsForTableOfOffers(array $emiDetails, $application): array
    {
        return array_values(array_filter($emiDetails, function ($emiDetail) use ($application) {
            return $this->isValidTableOfOffersAmount($emiDetail->loan_amount ?? null, $application);
        }));
    }

    private function filterValidMinimumTenureOffers($application, array $offers): array
    {
        return array_values(array_filter($offers, function ($offer) use ($application) {
            return $offer['status'] == 0
                && $this->isValidTableOfOffersAmount($offer['eligible_amount'] ?? null, $application);
        }));
    }

    private function filterDecisionTableOffersByEligibleAmount($application, array $offers): array
    {
        return array_values(array_filter($offers, function ($offer) use ($application) {
            return $this->isValidTableOfOffersAmount($offer['eligible_amount'] ?? null, $application);
        }));
    }

    private function isValidTableOfOffersAmount($amount, $application): bool
    {
        $loanAmount = $application->loan_amount ?? 0;

        if (!is_numeric($amount) || !is_numeric($loanAmount)) {
            return false;
        }

        return $amount >= 50000 && $amount <= $loanAmount;
    }

    public function saveToLoanDecisionApprovedAmount($id, $policy, $values) {
        LoanDecisionApprovedAmount::where(['application_id' => $id, 'policy_type' => $policy])->delete();
        $data = array_map(function ($item) use ($id, $policy) {
            return ['application_id' => $id,
            'offer_id' => $item->id,
            'policy_type' => $policy,
            'emi_amount' => $item->emi,
            'calculated_approved_amount' => $item->loan_amount,
            'created_at' => now(),
            'updated_at' => now(),
        ];}, $values);
        LoanDecisionApprovedAmount::insert($data);

        return LoanDecisionApprovedAmount::on('mysql_primary')
            ->where('application_id', $id)
            ->where('policy_type', $policy)        // also fixes the earlier filter mismatch
            ->orderByDesc('calculated_approved_amount')
            ->first();
    }

    private function updateOffersWithEligibleAmount(array &$offers, $filteredEmiDetails)
    {
        foreach ($offers as $key => $offer) {
            foreach ($filteredEmiDetails as $emiDetail) {
                if ($offer['offer_id'] == $emiDetail->id) {
                    $offers[$key]['eligible_amount'] = $emiDetail->loan_amount;
                    break;
                }
            }
        }
    }

    private function updateMinimumTenureOffersWithEligibleAmountAndEmi($application, array &$offers)
    {
        foreach ($offers as $key => $offer) {
            if ($offer['status'] != 0) {
                continue;
            }

            $offers[$key]['eligible_amount'] = $application->loan_amount;

            if (isset($this->emiData[$offer['offer_id']]['emi'])) {
                $offers[$key]['emi'] = $this->emiData[$offer['offer_id']]['emi'];
            }
        }
    }

    private function updateOffersWithBureauSurrogateLimit(array &$offers, array $bureauSurrogateLimit)
    {
        foreach ($offers as $key => $offer) {
            foreach ($bureauSurrogateLimit as $bureauLimit) {
                if ($offer['offer_id'] == $bureauLimit['id'] && $bureauLimit['loan_amount'] > $offer['bureau_surrogate_limit_amount']) {
                    $offers[$key]['bureau_surrogate_limit_emi'] = $bureauLimit['emi'];
                    $offers[$key]['bureau_surrogate_limit_amount'] = $bureauLimit['loan_amount'];
                }
            }
        }
    }

    private function checkApprovalStatus(array $offers)
    {
        foreach ($offers as $offer) {
            if (!$offer['status']) {
                return true;
            }
        }
        return false;
    }

    private function handleRejection($approvalStatus, $isAdminCall)
    {
        if (!$approvalStatus && !$isAdminCall) {
            $response = [
                'failed_condition' => null,
                'reason' => "Table of Offers failed"
            ];
            $this->saveApprovalStatus(0, $this->appId, null, 'failed', $response);
        }
    }

    private function handleException(\Exception $e)
    {
        $slackData = [
            'title' => 'TOF Auto assign offer Error',
            'fields' => [
                'error' => $e->getMessage(),
                'line' => $e->getLine(),
                'trace' => $e->getTraceAsString()
            ]
        ];

        (new \App\Slack(new Request($slackData)))->send();
    }

    public function getEligibleTenureOffer($application){
        $current_offer = $application->offer_id ?? 0;
        foreach ($this->emiData as $offerId => $emiDetails) {
            if($offerId == $current_offer){
                $this->eligibleTenureOffer = $offerId;
                $this->eligibleTenure = $emiDetails['tenure'];
                break;
            }
            if (isset($emiDetails['tenure']) && ($emiDetails['tenure'] < $this->eligibleTenure || $this->eligibleTenure == 0)) {
                $this->eligibleTenureOffer = $offerId;
                $this->eligibleTenure = $emiDetails['tenure'];
            }
        }
    }

    public function getNextBestAmountOfferWithCustomEMI($application, $custom_emi)
    {
        try {
            $scoreCard = new ScoreCard(new Request());

            // Prepare EMI calculation request
            $request = new Request();
            $request->attributes->add([
                'merchant_id'    => $application->merchant_id,
                'category_id'    => $application->purpose,
                'emi_amount'     => $custom_emi,
                'application_id' => $application->id,
                'table_of_offer_flow' => true
            ]);

            $calculation = new \App\Http\Controllers\Admin\EMICalculation($request);
            $emi         = $calculation->prepareCalculation();
            $emiDetails  = $emi->getData();

            return !empty($emiDetails) ? $emiDetails : [];

        } catch (\Exception $e) {
            (new \App\Slack(new Request([
                'title'  => 'TOF custom EMI assign error',
                'fields' => [
                    'error' => $e->getMessage(),
                    'line'  => $e->getLine(),
                    'trace' => $e->getTraceAsString(),
                ],
            ])))->send();
            return [];
        }
    }

    public function regenerateEMIAfterDecision($application,$final_offer_id,$req_id='',$source='minimum_tenure', $tofType=null){
        if($final_offer_id > 0) {
            $offer_details = MerchantOffers::with('disbursementOffersData')->where('id', $final_offer_id)->first();
        }

        $consumerApplicationController = new ConsumerApplicationController();
        $available_date = $consumerApplicationController->calculateFirstEMIDate($application, false, $offer_details ?? null);
        $emi_date = date('Y-m-d H:i:s', strtotime($available_date));

        $first_emi_date = $emi_date ?? $application->emi->emi_date;
        $loan_amount = $application->loan_amount ?? 0;
        $approved_amount = $application->approved_amount ?? 0;
        $category_id = $application->purpose ?? 0;
        $merchant_id = $application->merchant_id ?? 0;
        $previous_offer_id = $application->offer_id ?? 0;
        $previousOfferDetails = MerchantOffers::where('id', $previous_offer_id)->first();

        if (strlen($first_emi_date) > 0) {
            $converted_first_emi_date = Carbon::createFromFormat('Y-m-d H:i:s', $first_emi_date)->format('d/m/Y');
        }

        $approvalStatus = false;
        if ($final_offer_id > 0) {
            $request = new Request();
            $request->merge([
                'merchant_id' => $merchant_id,
                'loan_amount' => $approved_amount,
                'category_id' => $category_id,
                'merchant_offer_id' => $final_offer_id,
                'application_id' => $this->appId
            ]);

            $emiCalculation = new \App\Http\Controllers\Admin\EMICalculation($request);
            $new_emi_data = $emiCalculation->prepareCalculation($request, $this->appId);
            $decoded_new_emi_data = $new_emi_data->getData(true);

            // Calling RegenerateEMI function
            $requestData = new Request();
            $requestData->merge([
                "payable_by_customer" => $decoded_new_emi_data['payable'],
                "upfront_from_customer" => $decoded_new_emi_data['up_front_from_customer'],
                "tenure" => $decoded_new_emi_data['total_emi'],
                "remaining_emi" => $decoded_new_emi_data['emi_remaining'],
                "advance_emi" => $decoded_new_emi_data['advance_emi'],
                "interest_rate" => $decoded_new_emi_data['flat_annual_interest'],
                "subvention" => $decoded_new_emi_data['subvention'],
                "emi" => $decoded_new_emi_data['emi'],
                "upfront_from_credit_fair" => $decoded_new_emi_data['up_front_from_credit_fair'],
                "received_by_merchant" => $decoded_new_emi_data['total_amount_received_by_merchant'],
                "processing_fee" => $decoded_new_emi_data['processing_fee'],
                "subvention_cgst" => $decoded_new_emi_data['subvention_cgst'],
                "subvention_sgst" => $decoded_new_emi_data['subvention_sgst'],
                "subvention_igst" => $decoded_new_emi_data['subvention_igst'],
                "payment_frequency" => $decoded_new_emi_data['payment_frequency'],
                "disbursement_method" => $decoded_new_emi_data['disbursement_method'],
                "net_loan_amount" => $decoded_new_emi_data['net_loan_amount'],
                "first_emi_date" => $converted_first_emi_date,
                "offer_id" => $final_offer_id
            ]);

            $emiCalculation = new \App\Http\Controllers\Admin\EMICalculation($requestData);
            $emiCalculation->generateEMI($requestData, $this->appId);

            $username = isset(Auth::user()->name) ? Auth::user()->name : 'creditfair';
            if (!empty($tofType)){
                $this->addCommentCustomFlow($application, $tofType);
            }elseif($loan_amount != $application->approved_amount || $previous_offer_id != $final_offer_id){
                if (isset($previous_offer_id) && $previous_offer_id != $final_offer_id){
                    $comment = "Trigger Point - Edit FOIR page Submit button <br><br>
                    Applied Amount - Rs. ".$loan_amount." Applied Tenure - ".$previousOfferDetails->slug.", ".$previousOfferDetails->interest_rate."% interest, ".$previousOfferDetails->advance_emi." Advance EMIs <br>
                    Eligible Amount - Rs. ".$application->approved_amount." Eligible Tenure - ".$offer_details->slug.", ".$offer_details->interest_rate."% interest, ".$offer_details->advance_emi." Advance EMIs

                    @".$username." Please reach out to the @credit team if any additional documents are required.";

                }elseif($loan_amount != $application->approved_amount){
                    $comment = "Applied Amount - Rs. ".$loan_amount." Applied Tenure - ".$previousOfferDetails->slug.", ".$previousOfferDetails->interest_rate."% interest, ".$previousOfferDetails->advance_emi." Advance EMIs <br>
                    Eligible Amount - Rs. ".$application->approved_amount." Eligible Tenure - ".$offer_details->slug.", ".$offer_details->interest_rate."% interest, ".$offer_details->advance_emi." Advance EMIs

                    Please find below the list of documents required to process your original loan request:
                    <br/> 1. Last 6-months Bank statement of the applicant
                    <br/> 2. Earning co-app details <br/>

                    @".$username." Please reach out to the @credit team if any additional documents are required.";
                }

                $this->addComment($this->appId, Auth::user()->id ?? 107, $comment, 1, true);

                if($source == 'residual_emi'){
                    $comment = "Plan changed as per the next based amount TOF";
                }elseif ($source == 'bureau_surrogate_limit') {
                    $comment = "Plan changed as per the bureau surrogate limit TOF";
                }else{
                    $comment = "Plan changed as per the minimum tenure TOF";
                }
                $this->generateApprovalLog($this->appId, $comment,$req_id);
            }
            $approvalStatus = true;

        } else {
            $approvalStatus = false;
        }

        // Returning false because don't want to move the case to cf approved after regenereted emi case will move to waiting for decision
        return true;  // if returned true here the case will move to cf approved if false then waiting for decision
    }

    public function setFinalOffer($application) {
        // Gather all candidate eligibility sources.
        // TOFApprovedEligibility is only valid when policyPassed is not empty.
        $candidates = [];

        if (!empty($this->policyPassed) && !empty($this->TOFApprovedEligibility['approved_amount'])) {
            $candidates[] = array_merge($this->TOFApprovedEligibility, ['source' => 'minimum_tenure']);
        }

        if (!empty($this->bureauSurrogateEligibility['approved_amount'])) {
            $candidates[] = array_merge($this->bureauSurrogateEligibility, ['source' => $this->bureauSurrogateEligibility['table_of_offer_logic'] ?? 'bureau_surrogate_limit']);
        }

        if (!empty($this->powerSavingEligibility['approved_amount'])) {
            $candidates[] = array_merge($this->powerSavingEligibility, ['source' => 'POWER-SAVING']);
        }

        if (!empty($this->bankingSurrogateEligibility['approved_amount'])) {
            $candidates[] = array_merge($this->bankingSurrogateEligibility,['source' => 'BANKING-ABB']);
        }

        if (empty($candidates)) {
            return;
        }

        // Find the best candidate: highest approved_amount; on tie, lowest tenure.
        $best = null;
        foreach ($candidates as $candidate) {
            if ($best === null) {
                $best = $candidate;
                continue;
            }

            $candidateAmount = $candidate['approved_amount'] ?? 0;
            $bestAmount      = $best['approved_amount'] ?? 0;

            if ($candidateAmount > $bestAmount) {
                $best = $candidate;
            } elseif ($candidateAmount == $bestAmount) {
                // Tie-break: prefer the offer with the lowest tenure.
                $candidateTenure = $this->getOfferTenure($candidate['offer_id'] ?? null);
                $bestTenure      = $this->getOfferTenure($best['offer_id'] ?? null);

                if ($candidateTenure !== null && ($bestTenure === null || $candidateTenure < $bestTenure)) {
                    $best = $candidate;
                }
            }
        }

        if (empty($best)) {
            return;
        }

        $table_of_offer_logic = $best['source'];
        $final_offer_id       = $best['offer_id'] ?? null;
        $newApprovedAmount    = $best['approved_amount'] ?? null;
        $residualEMI          = $best['emi'] ?? null;
        $req_id               = $best['req_id'];
        $tofType              = in_array($table_of_offer_logic, ['POWER-SAVING', 'BANKING-ABB']) ? $table_of_offer_logic : null;

        if ($table_of_offer_logic === 'bureau_surrogate_limit') {
            // Persist bureau surrogate amount and update the EMI logic flag.
            $this->updateApplicationLogic($application, 'bureau_surrogate_limit', $residualEMI, $newApprovedAmount);
            $this->processOfferChange($application, $final_offer_id, $req_id, 'bureau_surrogate_limit');

        } elseif (in_array($table_of_offer_logic, ['POWER-SAVING', 'BANKING-ABB'])) {
            // Custom-EMI flows: store as residual_emi logic; tofType drives the comment via addCommentCustomFlow.
            // if (!$this->policyPassed) {
            //     $this->addNegativeResidualLog($application->id);
            // }
            $this->updateApplicationLogic($application, 'residual_emi', $residualEMI, $newApprovedAmount);
            $this->processOfferChange($application, $final_offer_id, $req_id, 'residual_emi', $tofType);

        } else {
            // minimum_tenure: only update the logic flag; no residual EMI or amount override needed.
            $this->updateApplicationLogic($application, 'minimum_tenure');
            $this->processOfferChange($application, $final_offer_id, $req_id, 'minimum_tenure');
        }
        return !empty($table_of_offer_logic);
    }

    /**
     * Retrieve the tenure (number of EMIs) for a given offer ID.
     * Checks $this->emiData first, then falls back to MerchantOffers.
     *
     * @param  int|null  $offerId
     * @return int|null
     */
    private function getOfferTenure($offerId): ?int
    {
        if (empty($offerId)) {
            return null;
        }

        if (isset($this->emiData[$offerId]['tenure']) && $this->emiData[$offerId]['tenure'] > 0) {
            return (int) $this->emiData[$offerId]['tenure'];
        }

        $offer = MerchantOffers::where('id', $offerId)->first(['tenure', 'advance_emi']);
        if (!$offer) {
            return null;
        }

        $tenure = (int) $offer->tenure;
        if ($offer->advance_emi && $offer->advance_emi > 0) {
            $tenure -= (int) $offer->advance_emi;
        }

        return $tenure > 0 ? $tenure : null;
    }

    public function filterBureauSurrogateByFOIR($application, $limit) {
        $filteredLimits = [];
        $merchantOffers = MerchantOffers::where('merchant_id', $application->merchant_id)->whereNull('deleted_at')->where('status', 1)->get();
        foreach ($merchantOffers as $offer) {
            // foreach ($this->bureauSurrogateLimits as $limit) {
                $emiCalculation = new \App\Http\Controllers\Admin\EMICalculation(new Request());
                $grossLoanAmount = $emiCalculation->getGrossLoanAmount($application->offer_id, $limit);
                $this->request->merge(['merchant_id' => $application->merchant_id, 'loan_amount' => $grossLoanAmount, 'merchant_offer_id' => $offer->id, 'category_id' => $application->purpose, 'interest_type_id' => $offer->interest_type_id, 'application_id' => $application->id, 'offer_obj' => $offer, 'charge_subvention_remaining_amount' => $application->merchant->charge_subvention_remaining_amount ?? 0, 'table_of_offer_flow' => true]);
                $_grade_ = new GradeController($this->request);

                $emi = new EMICalculation($this->request);
                $res = $emi->prepareCalculation();
                $res = $res->getData();

                $_grade_->cfFoir = 0;
                $_grade_->foirIncludingCFEMI = 0;
                foreach ($application->foir as $foir) {
                    if($foir->applicant_type != 2) {
                        $_grade_->setFOIRAttributes($foir);
                    }
                }
                $_grade_->getFOIRCalculation($application, $res->emi);

                if ($_grade_->foirIncludingCFEMI > $this->tofFoirLimit || $_grade_->cfFoir > $this->tofCfFoirLimit || $limit <= 50000) {
                    continue;
                }
                $filteredLimits[] = ['emi' => $res->emi, 'id' => $offer->id, 'loan_amount' => $grossLoanAmount];
            // }
        }
        return $filteredLimits;
    }


    public function runTOFWithCustomEMI($application){
        try {
            $this->setTOFAttributes($application, 0, 0, 0);
            $result = false;

            if (!empty($this->powerSavingPolicyPassed)) {
                $amount = $this->fetchCustomEMIAmount($application);
                $this->powerSavingEMI = $amount;
                if (is_numeric($amount) && $amount == 0) {
                    return true; // auto approve on applied loan amount
                }

                if (!is_null($amount) && $amount) {
                    $result = $this->processCustomEmiFlow($application, $amount, 'POWER-SAVING');
                }
            }

            if (!empty($this->bankingPolicyPassed)) {
                $amount = $this->fetchCustomEMIAmountForBanking($application);
                if (is_numeric($amount) && $amount == 0) {
                    return true; // auto approve on applied loan amount
                }

                if (!is_null($amount) && $amount) {
                    $result = $this->processCustomEmiFlow($application, $amount, 'BANKING-ABB');
                }
            }

            // Run processOfferChange once using the policy with the highest approved amount
            if ($result) {
                $bestDecision = LoanDecisionApprovedAmount::where('application_id', $application->id)
                    ->whereNull('deleted_at')
                    ->orderByDesc('calculated_approved_amount')
                    ->first();

                // if ($bestDecision && $bestDecision->offer_id) {
                    // $this->processOfferChange($application, $bestDecision->offer_id, '', 'residual_emi', $bestDecision->policy_type);
                // }
            }

            return $result;
        } catch (\Exception $e) {
            $cwFactory = new CloudWatchLoggerFactory();
            $cwFactory->logger(json_encode(['title' => 'Auto Decision: runCustomTOF Error', 'application_id' => $application->id, 'error' => $e->getMessage()]));
            return false;
        }
    }

    protected function processCustomEmiFlow($application, $customEmiAmount, $policyType){
        $table  = $this->buildDecisionTable();
        $offers = $this->processOffers($table);

        $processingResult = $this->handleEmptyEmiData($application, $offers, true, '', $customEmiAmount, $policyType, true);
        return $processingResult;
    }

    private function triggerTableOfOffersWebhook($application_id)
    {
        $webhookName = 'table-of-offers-webhook';
        $applicationController = new ConsumerApplicationController();

        $applicationController->webhook($application_id, [
            'type' => $webhookName,
            'status' => $webhookName,
            'data' => $applicationController->getMConnectPayload($application_id, $webhookName),
        ]);
    }

    public function setTOFAttributes($application, $tenureValue, $foirValue, $cf_foirValue){
        try{
            $this->Atenure = [];
            $this->Afoir = [];
            $this->Acf_foir = [];
            $this->tofFoirLimit = (isset($foirValue) && !empty($foirValue)) ? $foirValue : 0;
            $this->tofCfFoirLimit = (isset($cf_foirValue) && !empty($cf_foirValue)) ? $cf_foirValue : 0;
            if($this->foirIncludingCFEMI < 1 && $this->cfFoir < 1){
                foreach ($application->foir as $foir) {
                    $this->setFOIRAttributes($foir);
                }
                $this->getFOIRCalculation($application);
            }
            $products = array_map('intval', array_map('trim', explode(',', $application->application_element->product_name)));
            $productOffers = ProductOffers::whereIn('product_id', $products)->whereNull('deleted_at')->pluck('offer_id');
            if (!$productOffers->isEmpty()) {
                $offers = MerchantOffers::whereIn('id', $productOffers)->where('status', 1)->whereNull('deleted_at')->get();
            } else {
                $offers = MerchantOffers::where('merchant_id', $application->merchant_id)->whereNull('deleted_at')->where('status', 1)->get();
            }

            foreach ($offers as $offer) {
                $this->request->merge(['merchant_id' => $application->merchant_id, 'loan_amount' => $application->loan_amount, 'merchant_offer_id' => $offer->id, 'category_id' => $application->purpose, 'interest_type_id' => $offer->interest_type_id, 'application_id' => $application->id, 'offer_obj' => $offer, 'charge_subvention_remaining_amount' => $application->merchant->charge_subvention_remaining_amount ?? 0, 'table_of_offer_flow' => true]);
                $_grade_ = new GradeController($this->request);

                $emi = new EMICalculation($this->request);
                $res = $emi->prepareCalculation();
                $res = $res->getData();

                $this->cfFoir = 0;
                $this->foirIncludingCFEMI = 0;
                foreach ($application->foir as $foir) {
                    if($foir->applicant_type != 2) {
                        $_grade_->setFOIRAttributes($foir);
                    }
                }
                $_grade_->getFOIRCalculation($application, $res->emi);
                $this->emiData[$offer->id] = ['emi' => $res->emi, 'payable' => $res->payable, 'interest_rate' => $res->flat_annual_interest, 'processing_fee' => $res->processing_fee, 'upfront_from_customer' => $res->up_front_from_customer,'tenure'=> $res->emi_remaining, 'net_loan_amount' => $res->net_loan_amount, 'loan_amount' => $res->loan_amount];
                // TOF foir
                $_foir = (isset($foirValue) && !empty($foirValue)) ? $foirValue : 0;
                if ($_grade_->foirIncludingCFEMI > $_foir) {
                    $this->Afoir[] = ['offer_id' => $offer->id, 'foirIncludingCFEMI' => $_grade_->foirIncludingCFEMI, 'status' => 1];
                } else {
                    $this->Afoir[] = ['offer_id' => $offer->id, 'foirIncludingCFEMI' => $_grade_->foirIncludingCFEMI, 'status' => 0];
                }
                // TOF cf_foir
                $_cf_foir = (isset($cf_foirValue) && !empty($cf_foirValue)) ? $cf_foirValue : 0;
                if ($_grade_->cfFoir > $_cf_foir) {
                    $this->Acf_foir[] = ['offer_id' => $offer->id, 'cfFoir' => $_grade_->cfFoir, 'status' => 1];
                } else {
                    $this->Acf_foir[] = ['offer_id' => $offer->id, 'cfFoir' => $_grade_->cfFoir, 'status' => 0];
                }
                // TOF tenure
                $_tenure = $offer->tenure;
                if ($offer->advance_type == 'emi') {
                    $_tenure = $offer->tenure - $offer->advance_emi;
                }
                if ($tenureValue == 0 || empty($tenureValue) || (isset($application->emi) && $_tenure > $tenureValue)){
                    $this->Atenure[] = ['tenure' => !$_tenure ? 'no offer found' : $_tenure, 'offer_id' => $offer->id, 'status' => 1];
                }else{
                    $this->Atenure[] = ['tenure' => !$_tenure ? 'no offer found' : $_tenure, 'offer_id' => $offer->id, 'status' => 0];
                }
            }
        } catch (\Exception $e) {
            $cwFactory = new CloudWatchLoggerFactory();
            $cwFactory->logger(json_encode(['title' => 'Auto Decision: setTOFAttributes Error', 'application_id' => $application->id, 'error' => $e->getMessage()]));
            return false;
        }
    }

    public function fetchCustomEMIAmount($application) {
        try {
            $electrcityBill = DB::selectOne("SELECT uv.data FROM utility_validation uv WHERE consumer_application_id = ? ORDER BY created_at DESC LIMIT 1", [$application->id]);
            if (!empty($electrcityBill) && isset($electrcityBill->data)) {
                $billData = json_decode($electrcityBill->data, true);
                if (isset($billData['result']['bill_amount']) && is_numeric($billData['result']['bill_amount'])) {
                    $customEMIAmount = 1.5 * (int)$billData['result']['bill_amount'];
                }
            }
            if (!isset($customEMIAmount) || (isset($customEMIAmount) && $customEMIAmount < 0)) {
                $customEMIAmount = !empty($application->application_element->inverter_power_savings) ? 1.5 * $application->application_element->inverter_power_savings : null;
            }
            if ($this->request->has('source') && $this->request->get('source') == 'whatsapp_agent') {
                $billAmount = DB::selectOne("select bill_amount from ebill_ocr_data where application_id = ?", [$application->id])->bill_amount;
                return $billAmount;
            }
            if ($application->emi->emi < $customEMIAmount) {
                return 0; // return 0 when emi > than custom amount to get approved
            }
            return $customEMIAmount;
        } catch (\Exception $e) {
            $cwFactory = new CloudWatchLoggerFactory();
            $cwFactory->logger(json_encode(['title' => 'Auto Decision: fetchCustomEMIAmount Error', 'application_id' => $application->id, 'error' => $e->getMessage()]));
            return null;
        }
    }

    public function fetchCustomEMIAmountForBanking($application){
        try {

            $applicantFile = DB::selectOne(
                "SELECT file_path
                FROM consumer_application_files
                WHERE consumer_application_id = ?
                AND file_type = ?
                ORDER BY created_at DESC
                LIMIT 1",
                [$application->id, 'pirimid_analysis_json']
            );

            if (empty($applicantFile) || empty($applicantFile->file_path)) {
                return 0;
            }

            $s3 = Storage::disk('s3');
            $data = $s3->get($applicantFile->file_path);

            if (empty($data)) {
                return null;
            }

            $json = json_decode($data, true);

            if (!$json) {
                return null;
            }

            $averageAbb = $json['analytics']['overallAnalysis']['customAbbs']['adjustedAbb2']['averageOfMonthlyAverages'] ?? null;

            if (is_numeric($averageAbb)) {
                $averageAbb = max(0, (float) $averageAbb);
                $multiplier = (is_numeric($this->adjustedABBMultiplier) && (float)$this->adjustedABBMultiplier > 0)
                    ? (float)$this->adjustedABBMultiplier
                    : 0.5;
                $returnAmount = (float)$averageAbb * $multiplier;
            }

            if (isset($returnAmount) && $application->emi->emi < $returnAmount) {
                return 0;
            } else if (isset($returnAmount) && $application->emi->emi > $returnAmount) {
                return $returnAmount;
            }

            return null; // return null incase of no abb amount for key found

        } catch (\Exception $e) {

            $cwFactory = new CloudWatchLoggerFactory();
            $cwFactory->logger(json_encode([
                'title' => 'Auto Decision: fetchCustomEMIAmountForBanking Error',
                'application_id' => $application->id,
                'error' => $e->getMessage()
            ]));
            return null;
        }
    }

    public function getNextBasedAmountOffer($applicationDetails, $runAgain = false)
    {
        try {
            $scoreCard = new ScoreCard(new Request());
            if (!isset($applicationDetails->id)) {
                $applicationDetails = ConsumerApplication::with('emi')->find($applicationDetails);
            }

            $residual_emi = 0;

            if ($runAgain) {
                $residual_emi = $scoreCard->getEligibleEmiByResidualFOIR($applicationDetails->id);
            } else {
                $residual_emi = !empty($applicationDetails->emi->residual_emi) && $applicationDetails->emi->residual_emi > 0
                    ? $applicationDetails->emi->residual_emi
                    : $scoreCard->getEligibleEmiByResidualFOIR($applicationDetails->id);
            }

            if ($residual_emi < 0) {
                (new \App\Slack(new Request([
                'title'  => 'TOF next based amount assign error',
                'fields' => ['error' => 'Residual EMI is less than 0','residual_emi' => $residual_emi,'application_id' => $applicationDetails->id],])))->send();
                if (!empty($this->policyPassed)) {
                    $this->addNegativeResidualLog($this->appId, array_keys($this->policyPassed), 'Residual EMI is zero or negative');
                }
                return [];
            }

            // Prepare EMI calculation request
            $request = new Request();
            $request->attributes->add([
                'merchant_id'    => $applicationDetails->merchant_id,
                'category_id'    => $applicationDetails->purpose,
                'emi_amount'     => $residual_emi,
                'application_id' => $applicationDetails->id,
                'table_of_offer_flow' => true
            ]);

            $calculation = new \App\Http\Controllers\Admin\EMICalculation($request);
            $emi         = $calculation->prepareCalculation();
            $emiDetails  = $emi->getData();

            return !empty($emiDetails) ? $emiDetails : [];

        } catch (\Exception $e) {
            (new \App\Slack(new Request([
                'title'  => 'TOF next based amount assign error',
                'fields' => [
                    'error' => $e->getMessage(),
                    'line'  => $e->getLine(),
                    'trace' => $e->getTraceAsString(),
                ],
            ])))->send();
            return [];
        }
    }

    public function getBureauSurrogateDetails($applicationDetails, $runAgain = false)
    {
        try {
            if (!isset($applicationDetails->id)) {
                $applicationDetails = ConsumerApplication::with('emi')->find($applicationDetails);
            }

            // Prepare EMI calculation request
            $request = new Request();
            $request->attributes->add([
                'merchant_id'    => $applicationDetails->merchant_id,
                'category_id'    => $applicationDetails->purpose,
                'loan_amount'     => $applicationDetails->approved_amount,
                'application_id' => $applicationDetails->id,
                'table_of_offer_flow' => true
            ]);

            $calculation = new \App\Http\Controllers\Admin\EMICalculation($request);
            $emi         = $calculation->prepareCalculation();
            $emiDetails  = $emi->getData();

            return !empty($emiDetails) ? $emiDetails : [];

        } catch (\Exception $e) {
            (new \App\Slack(new Request([
                'title'  => 'Bureau Limit Approved Amount',
                'fields' => [
                    'error' => $e->getMessage(),
                    'line'  => $e->getLine(),
                    'trace' => $e->getTraceAsString(),
                ],
            ])))->send();
            return [];
        }
    }

    /**
     * Determine whether the passed policy for the application's latest request batch
     * (in jsonbre_approval_status_log) is a regular policy, i.e. it carries none of
     * the special rule types: power savings, emi multiplier on ABB or bureau
     * surrogate. Returns true when no special passed policy is present, in which case
     * the minimum_tenure / residual_emi offers may be included in the TOF payload.
     */
    public function tableOfOffersPassedPolicyIsRegular($applicationId)
    {
        $latestBatch = JsonBreApprovalStatusLog::where('application_id', $applicationId)
            ->orderBy('created_at', 'desc')
            ->first(['request_batch_id']);

        if (!$latestBatch || !$latestBatch->request_batch_id) {
            return true;
        }

        $passedRuleIds = JsonBreApprovalStatusLog::where('application_id', $applicationId)
            ->where('request_batch_id', $latestBatch->request_batch_id)
            ->where('status', 1)
            ->whereNotNull('rule_id')
            ->where('rule_id', '!=', 0)
            ->pluck('rule_id')
            ->unique()
            ->all();

        if (empty($passedRuleIds)) {
            return true;
        }

        $specialRuleKeys = ['power_savings', 'emi_multiplier_on_abb', 'bureau_surrogate'];

        $passedRules = ApprovalRule::whereIn('id', $passedRuleIds)->get(['rules']);

        foreach ($passedRules as $rule) {
            $ruleDetails = json_decode($rule->rules, true);
            if (!is_array($ruleDetails)) {
                continue;
            }
            foreach ($specialRuleKeys as $key) {
                if (isset($ruleDetails[$key]) && !empty($ruleDetails[$key])) {
                    return false;
                }
            }
        }

        return true;
    }
}
