<?php
/**
 * Created by PhpStorm.
 * User: abhishek
 * Date: 15/10/25
 * Time: 11:56 PM
 */

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Logging\CloudWatchLoggerFactory;
use Illuminate\Support\Facades\DB;
use App\Slack;
use App\BreLog;
use Carbon\Carbon;
use App\JsonBreApprovalStatusLog;
use Illuminate\Support\Facades\Storage;
use App\ConsumerApplication;
use App\ApprovalRule;
use App\ApplicationEMICalculation;
use App\DecisionTable;
use App\LoanDecisionApprovedAmount;
use App\Http\Controllers\Admin\EMICalculation;
use App\Http\Controllers\Grade\GradeController;
use App\PreEligibilityApplication;

class JsonBREController extends Controller 
{

    private $bre_rules_ignored = [];
    public $adjustedABBMultiplier = 0;
    private $cwFactory;

    public function __construct(){
        $this->cwFactory = new CloudWatchLoggerFactory();
    }

    public function createBrePayload($id) {
        $payload = array(
            'identifier' => $id,
            'grade' => array(
                'rules' => null
                )
            );
        return $payload;
    }

    public function get_exhaustive_criteria_json($file_path, $application_id){
        try {
            $s3 = Storage::disk('s3');
            $this->cwFactory->logger(json_encode(['title' => 'readFile checking', 'file_path' => $file_path, 'application_id'=>$application_id]));
            if ($s3->exists($file_path)) {
                $this->cwFactory->logger(json_encode(['title' => 'exhaustive_json file readFile exists', 'file_path' => $file_path, 'application_id'=>$application_id]));
                return $s3->get($file_path);
            }
            return false;
        } catch (\Exception $e) {
            $this->cwFactory->logger(json_encode(['title' => 'exhaustive_json file readFile does not exists', 'file_path' => $file_path, 'error' =>$e, 'application_id'=>$application_id]));
            return false;
        }
    }

    public function create_rule_list($application_id, $grade_rule, $req_id, $tableOfOffers, $exhaustive_criteria_json, $pre_eligibility=null){
        $rule_details = json_decode($grade_rule->rules);
        $rule_details = (array)$rule_details;
        if (array_key_exists('thin_file', $rule_details)) {
            // Skip the rules for NTC
            unset($rule_details['score']);
        }
        ksort($rule_details);
        $rule_details = (object)$rule_details;

        $rule_list = [];
        $applicationEmployment = DB::selectOne("SELECT employee_status from consumer_application where id = ?", [$application_id]) ?: (object) ['employee_status' => null];
        $applicationElement = DB::selectOne("SELECT electricity_name_change from consumer_application_element where application_id = ?", [$application_id]) ?: (object) ['electricity_name_change' => null];
        foreach ($rule_details as $key => $value) {
            try{
                if (!empty($pre_eligibility)) {
                    $pre_eligibility_key = $pre_eligibility.$key;
                    $key = !isset($exhaustive_criteria_json[$pre_eligibility_key]) ? $key : $pre_eligibility_key;
                }
                if (!in_array($exhaustive_criteria_json[$key], $rule_list)) {
                    if (($key == 'age_salaried' && $applicationEmployment->employee_status != 'SALARIED')) {
                        continue;
                    }
                    if ($key == 'age_self_employed' && !in_array($applicationEmployment->employee_status, ['Self Employed - Business', 'Self Employed - Professional'])) {
                        continue;
                    }
                    if ((str_contains($key, 'foir') || str_contains($key, 'cf_foir')) && $tableOfOffers) {
                        continue;
                    }
                    if ($key == 'e_bill_fuzzy_match' && isset($applicationElement->electricity_name_change) && $applicationElement->electricity_name_change == 1) {
                        continue;
                    }
                    $rule_list[] = $exhaustive_criteria_json[$key];  // Add the value to the rule list
                    if ($exhaustive_criteria_json[$key]){
                        $fallback_rule = $exhaustive_criteria_json[$key]['fallback'] ?? null;
                        if (is_array($fallback_rule) && isset($fallback_rule['rule'])){
                            $rule_list[] = $exhaustive_criteria_json[$fallback_rule['rule']];
                        }
                    }
                }
            }
            catch (\Exception $e) {
                $this->bre_rules_ignored[] = ['rule' => $key];
                (new Slack(new Request(['title' => 'Skipped a rule', 'fields' => ['error' => $e->getMessage(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString(), 'application_id' => $application_id], 'group' => Slack::$STAGE_TESTING])))->send();

            }
        }
        // add loan to value limit rule by default
        $rule_list[] = $exhaustive_criteria_json['loan_to_value_limit'];
        return $rule_list;
    }

    public function create_rejection_rule_list($application_id, $rejectRules, $exhaustive_criteria_json, $applicant_type=1, $nach_bounce="") {
        try{
            $rule_list = [];
            foreach ($rejectRules->new_rejection_rules as $key=>$reject_rule) {
                if (in_array($key, ['MAX_SCORE', 'MIN_SCORE'])) {
                    $rule_name = $applicant_type == 1 ? 'rejection_score' : 'co_applicant_rejection_score';
                } else if (in_array($key, ['MAX_AGE', 'MIN_AGE'])) {
                    $rule_name = $applicant_type == 1 ? 'rejection_age' : 'co_applicant_rejection_age';
                } else if ($key == 'NEGATIVE_AREA') {
                    if ($reject_rule) {
                        $rule_name = $applicant_type == 1 ? 'negative_area' : 'co_applicant_negative_area';
                    }
                } else if ($key == 'APPROVED_PINCODE') {
                    if ($reject_rule) {
                        $rule_name = $applicant_type == 1 ? 'is_pincode_approved' : 'co_applicant_is_pincode_approved';
                    }
                } elseif ($applicant_type == 2 && $key == 'MONTHLY_INCOME') {
                    $rule_name = 'co_applicant_rejection_monthly_income';
                } else {
                    $rule_name = 'rejection_'.strtolower($key);
                }
                if (!in_array($exhaustive_criteria_json[$rule_name], $rule_list)) {
                    if ($rule_name == 'rejection_nach_bounce' && empty($nach_bounce)) {
                        continue;
                    }
                    $rule_list[] = $exhaustive_criteria_json[$rule_name];
                    if ($exhaustive_criteria_json[$rule_name]){
                        $fallback_rule = $exhaustive_criteria_json[$rule_name]['fallback'] ?? null;
                        if (is_array($fallback_rule) && isset($fallback_rule['rule'])){
                            $rule_list[] = $exhaustive_criteria_json[$fallback_rule['rule']];
                        }
                    }
                }
            }
            return $rule_list;
        } catch (\Exception $e) {
            $this->bre_rules_ignored[] = ['rule' => $key];
            (new Slack(new Request(['title' => 'Skipped a rule', 'fields' => ['error' => $e->getMessage(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString(), 'application_id' => $application_id], 'group' => Slack::$STAGE_TESTING])))->send();
        }
    }

    public function runJsonBreApproval($application_id, $req_id, $gradeID, $tableOfOffers, $json_bre_for_log=false, $request=null) {
        try{
            $sql = "SELECT id, rule_name, rules from approval_rule where grade_id = ? and status = 1";
            $bindings = [$gradeID];

            $rule_filter = $request->attributes->get('rule_filter');

            if ($rule_filter) {
                $sql .= " AND rules LIKE ?";
                $bindings[] = '%' . $rule_filter . '%';
            }
            $grade_rules = DB::select($sql, $bindings);

            $bre_api_payload = $this->createBrePayload($application_id);
            $bre_api_payload['applicant_type'] = 1; // Default for applicant in this method

            $is_pre_eligibility = $request && $request->attributes->get('pre_eligibility') ? true : false;
            if ($is_pre_eligibility) {
                $bre_api_payload['pre_eligibility'] = true;
                $application = PreEligibilityApplication::with('creditReport', 'merchant')->where('id', $application_id)->first();
                if ($application) {
                    // shim properties so the shared policy-payload block can read uniform field names
                    $application->approved_amount = $application->loan_amount;
                    $application->name = $application->customer_name;
                }
                $netLoanAmount = $application ? (int) $application->loan_amount : 0;
                $pre_eligibility_prefix = 'pre_eligibility_';
            } else {
                $application = ConsumerApplication::with('emi', 'application_element', 'creditReport')->where('id', $application_id)->first();
                $netLoanAmount = $application->approved_amount - $application->emi->upfront_from_customer;
                $pre_eligibility_prefix = '';
            }

            $pirimidData = DB::selectOne(
                "SELECT id, tracking_id, response
                FROM pirimid_bank_analysis
                WHERE application_id = ? AND analytics_fetch = 1 AND applicant_type = 1
                ORDER BY created_at DESC
                LIMIT 1",
                [$application_id]
            );
            
            $trackingId = null;
            if (isset($pirimidData->id)) {
                if (!empty($pirimidData->tracking_id)) {
                    $trackingId = $pirimidData->tracking_id;
                } 
                else if (!empty($pirimidData->response)) {
                    $response = json_decode($pirimidData->response, 1);
                    $trackingId = $response['trackingId'] ?? null;
                }
            }
            
            if ($trackingId) {
                $bre_api_payload['pirimid_tracking_id'] = $trackingId;
            }

            $accountAggregatorId = $request ? $request->attributes->get('account_aggregator_id') : null;
            if (is_null($trackingId) && $accountAggregatorId) {
                $bre_api_payload['account_aggregator_id'] = $accountAggregatorId;
            }

            $file_path = _env('JSON_BRE_EXHAUSTIVE_JSON');
            $exhaustive_criteria_json = $this->get_exhaustive_criteria_json($file_path, $application_id);
            $exhaustive_criteria_json = json_decode($exhaustive_criteria_json, 1);
            foreach ($grade_rules as $grade_rule) {
                $rule = json_decode($grade_rule->rules, 1);
                $policy = [];
                if (isset($rule['emi_multiplier_on_abb']) && !empty($rule['emi_multiplier_on_abb']) && $rule['emi_multiplier_on_abb'] != 0) {

                    if (empty($trackingId) && empty($accountAggregatorId)) {
                        $this->cwFactory->logger(json_encode([
                            'title' => 'Skipping ABB Policy - No Pirimid Tracking ID/Bank Statement', 
                            'application_id' => $application_id, 
                            'rule_id' => $grade_rule->id
                        ]));
                        continue; 
                    }

                    $this->adjustedABBMultiplier = (float)$rule['emi_multiplier_on_abb'];
                    $policy_name = "policy-adjusted-abb-" . $grade_rule->id;
                } elseif (isset($rule['power_savings'])) {
                    $policy_name = "policy-power-savings-" . $grade_rule->id;
                } elseif (isset($rule['bureau_surrogate'])) {
                    $policy_name = "policy-bureau-surrogate-" . $grade_rule->id;
                }
                else {
                    $policy_name = "policy-" . $grade_rule->id;
                }

                $rule_list = $this->create_rule_list($application_id, $grade_rule, $req_id, $tableOfOffers, $exhaustive_criteria_json);
                if (isset($accountAggregatorId) && !empty($accountAggregatorId)) {
                    $rule_list = array_map(function($item) {
                        $encoded = json_encode($item);
                        $replaced = str_replace('pirimid', 'account_aggregator', $encoded);
                        return json_decode($replaced, true);
                    }, $rule_list);
                }
                if($rule_list){
                    $policy['rule'] = $rule_list;
                }
                else{
                    $policy['rule'] = [];
                }
                $policy['payload'] = array( 'identifier' => $application_id
                                            , 'applicant_type' => 1
                                            , 'rule_name' => $grade_rule->rule_name
                                            , 'loan_amount' => isset($application->approved_amount) ? (int)($application->approved_amount) : null
                                            // , 'max_loan_amount' => isset($rule['max_loan_amount']) ? (int)($rule['max_loan_amount']) : null
                                            // , 'min_loan_amount' => isset($rule['min_loan_amount']) ? (int)($rule['min_loan_amount']) : null
                                            , 'employee_status' => isset($rule['employee_status']) ? ($rule['employee_status']) : null
                                            , 'past_dues' => isset($rule['past_dues']) ? (int)($rule['past_dues']) : null
                                            , 'credit_card_percentage' => isset($rule['credit_card_amount_paid']) ? (int)($rule['credit_card_amount_paid']) : null
                                            , 'age' => isset($rule['age']) ? (int)($rule['age']) : null
                                            , 'ageAtLastEMI' => isset($rule['ageAtLastEMI']) ? (int)($rule['ageAtLastEMI']) : null
                                            , 'monthly_income' => isset($rule['monthly_income']) ? (int)($rule['monthly_income']) : null
                                            , 'score' => isset($rule['score']) ? (int)($rule['score']) : null
                                            // , 'tenure' => isset($rule['tenure']) ? (int)($rule['tenure']) : null
                                            // , 'cf_foir' => isset($rule['cf_foir']) ? (int)($rule['cf_foir']) : null
                                            // , 'foir' => isset($rule['foir']) ? (int)($rule['foir']) : null
                                            , 'case_type' => isset($rule['caseType']) ? ($rule['caseType']) : null
                                            , 'dpd_years_above_0' => isset($rule['dpd_years_above_zero']) ? (int)($rule['dpd_years_above_zero']) : null
                                            , 'dpd_count_above_0' => isset($rule['dpd_count_above_zero']) ? (int)($rule['dpd_count_above_zero']) : null
                                            , 'pre_dpd_years' => isset($rule['pre_dpd_years']) ? (int)($rule['pre_dpd_years']) : null
                                            , 'pre_dpd_count' => isset($rule['pre_dpd_count']) ? (int)($rule['pre_dpd_count']) : null
                                            , 'pre_dpd_keywords' => isset($rule['pre_dpd_keywords']) ? ($rule['pre_dpd_keywords']) : null
                                            , 'post_dpd_year' => isset($rule['post_dpd_year']) ? (int)($rule['post_dpd_year']) : null
                                            , 'post_dpd_count' => isset($rule['post_dpd_count']) ? (int)($rule['post_dpd_count']) : null
                                            , 'post_dpd_keywords' => isset($rule['post_dpd_keywords']) ? ($rule['post_dpd_keywords']) : null
                                            // , 'surrogate_percentage' => isset($rule['bureau_surrogate']) ? (int)($rule['bureau_surrogate']) : null
                                            , 'term_loan_amount_paid_percentage' => isset($rule['term_loan_amount_paid']) ? (int)($rule['term_loan_amount_paid']) : null
                                            , 'power_savings' => isset($application->application_element->inverter_power_savings) ? (float)($application->application_element->inverter_power_savings) : null
                                            , 'emi' => isset($application->emi->emi) ? (int)($application->emi->emi) : null
                                            , 'zero_downpayment' => 0
                                            , 'bank_validation' => isset($rule['bank_validation']) ? (int)($rule['bank_validation']) : null
                                            // New rules payload added
                                            , 'solar_financing_for' => isset($rule['solar_financing_for']) ? $rule['solar_financing_for'] : null
                                            , 'solar_power_type' => isset($rule['solar_power_type']) ? $rule['solar_power_type'] : null
                                            , 'kw_capping' => isset($rule['kw_capping']) ? $rule['kw_capping'] : false
                                            , 'bureau_type' => $application->creditReport[0]->bureau
                                            , 'keyword_rules' => isset($rule['keyword_rules']) ? ($rule['keyword_rules']) : null
                                            , 'account_status_rules' => isset($rule['account_status_rules']) ? ($rule['account_status_rules']) : null
                                            , 'dpd_rules' => isset($rule['dpd_rules']) ? ($rule['dpd_rules']) : null
                                            , 'past_dues_rules' => isset($rule['past_dues_rules']) ? ($rule['past_dues_rules']) : null
                                            , 'principal_written_off_rules' => isset($rule['principal_written_off_rules']) ? ($rule['principal_written_off_rules']) : null
                                            , 'bureau_surrogate' => isset($rule['bureau_surrogate']) ? $rule['bureau_surrogate'] : null
                                            , 'age_self_employed' => isset($rule['age_self_employed']) ? array_map('intval', $rule['age_self_employed']) : null
                                            , 'age_salaried' => isset($rule['age_salaried']) ? array_map('intval', $rule['age_salaried']) : null
                                            , 'max_net_tenure' => isset($rule['max_net_tenure']) ? (int)($rule['max_net_tenure']) : null
                                            , 'min_net_tenure' => isset($rule['min_net_tenure']) ? (int)($rule['min_net_tenure']) : null
                                            , 'max_net_loan_amount' => isset($rule['max_net_loan_amount']) ? (int)($rule['max_net_loan_amount']) : null
                                            , 'min_net_loan_amount' => isset($rule['min_net_loan_amount']) ? (int)($rule['min_net_loan_amount']) : null
                                            , 'track_record' => isset($rule['track_record']) ? $rule['track_record'] : null
                                            , 'e_bill_fuzzy_match' => isset($rule['e_bill_fuzzy_match']) ? (int)$rule['e_bill_fuzzy_match'] : 0
                                            , 'net_loan_amount' => $netLoanAmount ?? 0
                                            , 'declared_monthly_income' => isset($rule['speculative_txn_cap_pct']) ? (int)($application->monthly_income) : null
                                            , 'applicant_name' => isset($rule['name_match_threshold_pct']) ? $application->name : null
                                            , 'min_banking_months' => isset($rule['min_banking_months']) ? (int)$rule['min_banking_months'] : null
                                            , 'name_match_threshold_pct' => isset($rule['name_match_threshold_pct']) ? (int)$rule['name_match_threshold_pct'] : null
                                            , 'emi_bounce_rule' => isset($rule['emi_bounce_rule']['max_emi_bounces']) ? (int)$rule['emi_bounce_rule']['max_emi_bounces'] : null
                                            , 'emi_bounce_lookback_months' => isset($rule['emi_bounce_rule']['emi_bounce_lookback_months']) ? (int)$rule['emi_bounce_rule']['emi_bounce_lookback_months'] : 3
                                            , 'speculative_txn_cap_pct' => isset($rule['speculative_txn_cap_pct']) ? (float)$rule['speculative_txn_cap_pct'] : null
                                            , 'loan_to_value_limit' => $application->merchant->loan_to_value_limit
                                            );
                if (isset($rule['thin_file']) || isset($rule['pre_eligibility_thin_file'])) {
                    $policy['payload']['thin_file_score'] = [-1, 10]; // thin file values set
                    // unset($policy['payload']['score']);
                }
                if (!$tableOfOffers) {
                    $policy['payload']['tenure'] = isset($rule['tenure']) ? (int)($rule['tenure']) : null;
                    $policy['payload']['cf_foir'] = isset($rule['cf_foir']) ? (int)($rule['cf_foir']) : null;
                    $policy['payload']['foir'] = isset($rule['foir']) ? (int)($rule['foir']) : null;
                }
                if ($is_pre_eligibility) {
                    $policy['payload']['tenure'] = $application->tenure;
                }
                $this->cwFactory->logger(json_encode(['title' => 'BRE_API payload addition attempted', 'application_id' => $application_id, 'rule'=>$grade_rule->id]));
                if (isset($policy)){
                    $bre_api_payload['grade']['rules'][$policy_name] = $policy;
                }
            }
            if($json_bre_for_log) {
                // Rejection rules for JSON BRE
                $auto_reject_rule = DB::selectOne("SELECT modifications from auto_reject_rule where grade_id = ? order by created_at desc", [$gradeID]);
                $reject_rules = json_decode($auto_reject_rule->modifications);
                $rejectionPolicy = [];
                $rejectionPolicy['payload'] = array(
                    'identifier' => $application_id,
                    'applicant_type' => 1,
                    'rule_name' => 'auto-rejection-rule',
                    'age_range' => [(int)$reject_rules->new_rejection_rules->MIN_AGE, (int)$reject_rules->new_rejection_rules->MAX_AGE],
                    'monthly_income' => isset($rule['monthly_income']) ? (int)($rule['monthly_income']) : null,
                    'credit_score_range' => [(int)$reject_rules->new_rejection_rules->MIN_SCORE, (int)$reject_rules->new_rejection_rules->MAX_SCORE],
                    'bureau_type' => $application->creditReport[0]->bureau,
                    'dpd_rejection_rules' => $reject_rules->new_rejection_rules->DPD[0],
                    'keyword_rejection_rules' => $reject_rules->new_rejection_rules->KEYWORD[0],
                    'account_status_rejection_rules' => $reject_rules->new_rejection_rules->ACCOUNT_STATUS[0],
                    'principal_write_off_rules' => $reject_rules->new_rejection_rules->PRINCIPAL_WRITTEN_OFF_AMOUNT[0],
                    'total_past_dues_rules' => $reject_rules->new_rejection_rules->TOTAL_PAST_DUES[0],
                    'nach_bounce_rule' => $reject_rules->new_rejection_rules->NACH_BOUNCE
                );

                $policy_name = "policy-rejection";
                $rule_list = $this->create_rejection_rule_list($application_id, $reject_rules, $exhaustive_criteria_json, 1, $trackingId);
                if($rule_list){
                    $rejectionPolicy['rule'] = $rule_list;
                }
                $bre_api_payload['grade']['rules'][$policy_name] = $rejectionPolicy;
            }
            try{
                $response = $this->runAutoDecisionBRE($bre_api_payload);
                if(!$json_bre_for_log){
                    $this->store_jsonbre_response($response, $req_id, false, false, (int)$is_pre_eligibility);
                }
                return $response;
            }
            catch(\Exception $e) {
                $this->cwFactory->logger(json_encode(['title' => 'BRE_API run Failed', 'application_id' => $application_id, 'req_id'=> $req_id]));
            }
        }
        catch(\Exception $e) {
            $this->cwFactory->logger(json_encode(['title' => 'Create BRE Payload failed', 'application_id' => $application_id, 'error'=>$e->getMessage(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString()]));
        }
    }

    public function runRejection($application_id, $req_id, $auto_reject_rule, $json_bre_for_log=false, $pirimid_tracking_id='', $applicant_type = 1, $account_aggregator_id='') {
        try{
            $bre_api_payload = $this->createBrePayload($application_id);
            $bre_api_payload['applicant_type'] = $applicant_type;
            $file_path = _env('JSON_BRE_EXHAUSTIVE_JSON');
            $exhaustive_criteria_json = $this->get_exhaustive_criteria_json($file_path, $application_id);
            $exhaustive_criteria_json = json_decode($exhaustive_criteria_json, 1);

            $creditReport = DB::selectOne("SELECT id, bureau from credit_report where application_id = ? and applicant_type = ?", [$application_id, $applicant_type]);
            $modifications = json_decode($auto_reject_rule->modifications, true);

            $policiesToCheck = [];
            if ($applicant_type == 2) {
                // For co-applicant, we might have both income and signing rules in the same JSON
                if (isset($modifications['co_applicant_for_income'])) {
                    $policiesToCheck['policy-rejection-income'] = $modifications['co_applicant_for_income'];
                }
                if (isset($modifications['co_applicant_for_signing'])) {
                    $policiesToCheck['policy-rejection-signing'] = $modifications['co_applicant_for_signing'];
                }
                
                // Backward compatibility for old co-applicant records
                if (empty($policiesToCheck)) {
                    $policiesToCheck['policy-rejection'] = $modifications;
                }
            } else {
                $policiesToCheck['policy-rejection'] = $modifications;
            }

            foreach ($policiesToCheck as $policyName => $reject_rules) {
                $reject_rules = (object)$reject_rules;
                $rejectionPolicy = [];
                $rejectionPolicy['payload'] = array(
                    'identifier' => $application_id,
                    'applicant_type' => $applicant_type,
                    'rule_name' => 'auto-rejection-rule',
                    'age_range' => [(int)$reject_rules->new_rejection_rules['MIN_AGE'], (int)$reject_rules->new_rejection_rules['MAX_AGE']],
                    'monthly_income' => isset($reject_rules->new_rejection_rules['MONTHLY_INCOME']) ? (int)($reject_rules->new_rejection_rules['MONTHLY_INCOME']) : null,
                    'credit_score_range' => [(int)$reject_rules->new_rejection_rules['MIN_SCORE'], (int)$reject_rules->new_rejection_rules['MAX_SCORE']],
                    'bureau_type' => $creditReport->bureau,
                    'dpd_rejection_rules' => $reject_rules->new_rejection_rules['DPD'][0],
                    'keyword_rejection_rules' => $reject_rules->new_rejection_rules['KEYWORD'][0],
                    'account_status_rejection_rules' => $reject_rules->new_rejection_rules['ACCOUNT_STATUS'][0],
                    'principal_write_off_rules' => $reject_rules->new_rejection_rules['PRINCIPAL_WRITTEN_OFF_AMOUNT'][0],
                    'total_past_dues_rules' => $reject_rules->new_rejection_rules['TOTAL_PAST_DUES'][0]
                );
                
                if ($pirimid_tracking_id == ''){
                    $pirimidAnalysisAvailable = DB::selectOne("SELECT id, response from pirimid_bank_analysis where application_id = ? and applicant_type = ? and analytics_fetch = 1 order by created_at desc", [$application_id, $applicant_type]);
                    if (isset($pirimidAnalysisAvailable->id)) {
                        $pResponse = json_decode($pirimidAnalysisAvailable->response, 1);
                        if (isset($pResponse['trackingId'])) {
                            $pirimid_tracking_id =  $pResponse['trackingId'];
                        }
                    }
                }
                
                // TODO - Incase pirimid tracking id is present then the account aggregator flow will not work.
                if (isset($pirimid_tracking_id) && $pirimid_tracking_id != '') {
                    $bre_api_payload['pirimid_tracking_id'] = $pirimid_tracking_id;
                    $rejectionPolicy['payload']['nach_bounce_rule'] = $reject_rules->new_rejection_rules['NACH_BOUNCE'];
                }

                if ($pirimid_tracking_id == '' && isset($account_aggregator_id) && $account_aggregator_id != '') {
                    $bre_api_payload['account_aggregator_id'] = $account_aggregator_id;
                }

                $rule_list = $this->create_rejection_rule_list($application_id, (object)$reject_rules, $exhaustive_criteria_json, $applicant_type,$pirimid_tracking_id);
                if($rule_list){
                    $rejectionPolicy['rule'] = $rule_list;
                }
                $bre_api_payload['grade']['rules'][$policyName] = $rejectionPolicy;
            }
            $response = $this->runAutoDecisionBRE($bre_api_payload);
            if(!$json_bre_for_log){
                $this->store_jsonbre_response($response, $req_id, true);
            }
            return $response;
        }
        catch(\Exception $e){
            $this->cwFactory->logger(json_encode(['application_id'=> $application_id, 'error'=>$e->getMessage(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString()]));
        }
    }

    public function store_jsonbre_response($response, $req_id, $rejection=false, $reasonDetails=false, $is_pre_eligibility=0) {
        if ($reasonDetails) {
            $this->cwFactory->logger(json_encode(['title' => 'Default check failed', 'reason'=>$reasonDetails['reason'], 'application_id' => $reasonDetails['application_id']]));
            $logv2 = new JsonBreApprovalStatusLog();
            $logv2->rule_id = 0;
            $logv2->application_id = $reasonDetails['application_id'];
            $logv2->reason = $reasonDetails['reason'];
            $logv2->request_batch_id = $req_id;
            $logv2->status = 0;
            $logv2->created_at = Carbon::now()->toDateTimeString();
            $logv2->save();
            return;
        }
        $data = $response->getData();
        $decoded_response = isset($data->res) ? json_decode($data->res) : "";
        $bre_api_payload = isset($data->req) ? $data->req : "";
        $this->cwFactory->logger(json_encode(['title' => 'Storing BRE Logs', 'application_id' => $bre_api_payload->identifier, 'req_id'=> $req_id, 'request'=>$data->req, 'response'=>$data->res, 'rules_ignored'=>$this->bre_rules_ignored]));
        $log = new BreLog();
        $log->application_id = $bre_api_payload->identifier;
        $log->applicant_type = isset($bre_api_payload->applicant_type) ? (int)$bre_api_payload->applicant_type : 1;
        $log->request = json_encode($data->req);
        $log->response = json_encode($decoded_response);
        $log->rule_ignored = json_encode($this->bre_rules_ignored);
        $log->created_at = Carbon::now()->toDateTimeString();
        $log->request_batch_id = $req_id;
        $log->pre_eligibility = $is_pre_eligibility;
        $log->save();
        if($rejection) {
            return;
        }
        if (!empty($decoded_response)) {
            foreach ($decoded_response as $key => $value) {
                // Store the key as a variable
                if ($key === 'status') {
                    continue; // This skips the rest of the loop for this iteration
                }
                $logv2 = new JsonBreApprovalStatusLog();
                $pattern = '/(\d+)$/';
                $matches = [];
                $rule_id = preg_match($pattern, $key, $matches) ? (int)$matches[1] : 0;
                $logv2->rule_id = $rule_id;
                $logv2->application_id = $bre_api_payload->identifier;
                $logv2->failed_condition = isset($value->failed_condition) ? $value->failed_condition->key : "";
                $logv2->reason = isset($value->failed_condition->reason)? $value->failed_condition->reason : "";
                $logv2->request_batch_id = $req_id;
                $logv2->status=(int)$value->policy_status;
                $logv2->created_at = Carbon::now()->toDateTimeString();
                $logv2->save();
            }
        }
    }

    public function getJsonApprovalLogs($id)
    {
        $approvalCheck = JsonBreApprovalStatusLog::where('application_id', $id)->orderBy('created_at', 'desc')->first();
        if (!isset($approvalCheck->id)) {
            return false;
        }
        $result = $this->checkIfBankingPolicy($approvalCheck->rule_id);

        if ($result) {
            // Get ALL distinct request_batch_ids ordered by latest first
            $allRequestIds = JsonBreApprovalStatusLog::select('request_batch_id')
                ->where('application_id', $id)
                ->groupBy('request_batch_id')
                ->orderByRaw('MAX(created_at) DESC')
                ->pluck('request_batch_id')
                ->toArray();

            // The latest batch is always a banking policy (we already checked above).
            // Walk through the remaining batches (starting from index 1 = second-last)
            // and skip any that are also banking policy, until we find one that is not.
            $latestRequestId = $allRequestIds[0];
            $nonBankingRequestId = null;

            for ($i = 1; $i < count($allRequestIds); $i++) {
                // Get the representative rule_id for this batch (use its most recent log entry)
                $batchLatestLog = JsonBreApprovalStatusLog::where('application_id', $id)
                    ->where('request_batch_id', $allRequestIds[$i])
                    ->orderBy('created_at', 'desc')
                    ->first();

                if ($batchLatestLog && !$this->checkIfBankingPolicy($batchLatestLog->rule_id)) {
                    // Found the first non-banking-policy batch
                    $nonBankingRequestId = $allRequestIds[$i];
                    break;
                }
                // This batch is also banking policy — skip it and continue
            }

            // Build query using the latest batch + the found non-banking batch (if any)
            $requestIdsToFetch = [$latestRequestId];
            if ($nonBankingRequestId !== null) {
                $requestIdsToFetch[] = $nonBankingRequestId;
            }

            return JsonBreApprovalStatusLog::with('rule')
                ->where('application_id', $id)
                ->whereIn('request_batch_id', $requestIdsToFetch)
                ->orderBy('created_at', 'desc')
                ->get();
        } else {
            // Use existing logic — latest batch is not a banking policy
            return JsonBreApprovalStatusLog::with('rule')
                ->where('application_id', $id)
                ->where('request_batch_id', $approvalCheck->request_batch_id)
                ->get();
        }
        return false;
    }

    public function checkIfBankingPolicy($rule_id) {
        // Query the approvalRule table using the rule_id
        $approvalRule = DB::table('approval_rule')->where('id', $rule_id)->first();
        
        // Check if the rule exists and if the rules column contains emi_multiplier_on_abb key
        if ($approvalRule && isset($approvalRule->rules)) {
            $rules = json_decode($approvalRule->rules, true);
            return isset($rules['emi_multiplier_on_abb']) && !empty($rules['emi_multiplier_on_abb']);
        }
        
        return false;
    }

    public function fetchLatestJsonBreResponse($application_id){
        // Get the relevant request_batch_ids from getJsonApprovalLogs
        $approvalLogs = $this->getJsonApprovalLogs($application_id);

        if ($approvalLogs && $approvalLogs->isNotEmpty()) {
            // Extract unique request_batch_ids from the approval logs
            $requestBatchIds = $approvalLogs->pluck('request_batch_id')->unique()->filter()->values()->toArray();

            if (!empty($requestBatchIds)) {
                // Fetch bre_logs that match the request_batch_ids
                $bre_logs = DB::select(
                    "SELECT id, response, request_batch_id FROM bre_logs
                     WHERE application_id = ? AND applicant_type = 1
                     AND request_batch_id IN (" . implode(',', array_fill(0, count($requestBatchIds), '?')) . ")
                     ORDER BY created_at DESC",
                    array_merge([$application_id], $requestBatchIds)
                );

                if (count($bre_logs) > 0) {
                    return $bre_logs;
                }
            }
        }

        // Fallback: original logic — return the first non-rejection log
        $bre_log = DB::select("SELECT id, response from bre_logs where application_id = ? and applicant_type = 1 order by created_at desc", [$application_id]);
        if (count($bre_log) > 0) {
            foreach ($bre_log as $log) {
                $decoded_response = json_decode($log->response, 1);
                // display only the approval logs not the rejection logs
                if (!isset($decoded_response['policy-rejection'])) {
                    return $log;
                }
            }
        }
        return false;
    }

    public function runAutoDecisionBRE($payload) {
        $curl = curl_init();
        curl_setopt_array($curl, array(
            CURLOPT_URL => _env('BRE_API_UAT_URL'),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => "",
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 120,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => "POST",
            CURLOPT_POSTFIELDS => json_encode($payload),
            CURLOPT_HTTPHEADER => array(
                "Content-Type: application/json",
            ),
        ));
        $res = curl_exec($curl);
        $data = json_decode($res, 1);
        $httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        $err = curl_error($curl);
        curl_close($curl);
        return response()->json(['req' => $payload, 'res'=> $res, 'code' => $httpcode], 200);
    }

    /**
     * Calculate the eligible amount for a JSON BRE policy.
     *
     * The rule_id (taken from the JsonBreApprovalStatusLog / JSON BRE policy key) is used
     * to fetch the policy from the approval_rule table. The policy type is then derived from
     * its rules JSON the same way JSON BRE policy keys are generated
     * (see JsonBREController::runJsonBreApproval):
     *   - emi_multiplier_on_abb set  -> banking surrogate (adjusted-abb)
     *   - power_savings set          -> power saving
     *   - bureau_surrogate set       -> bureau surrogate
     *
     * NOTE: the amounts returned here are static placeholders for now. The actual amount
     * calculation logic for each policy type will be implemented later.
     *
     * @param  int|string  $ruleId         rule_id of the policy
     * @param  int         $applicationId  application id (for upcoming calculation logic)
     * @return float
     */
    public function calculateEligibleAmount($ruleId, $applicationId)
    {
        $application = ConsumerApplication::with('emi', 'application_element', 'offer')->where('id', $applicationId)->first();
        $rule = ApprovalRule::find($ruleId);
        if (!$rule) {
            return 0;
        }
        $ruleConfig = json_decode($rule->rules, true) ?: [];

        if (!empty($ruleConfig['emi_multiplier_on_abb'])) {
            // Banking surrogate (adjusted-abb) policy
            $eligibleAmount = $this->bankingSurrogateEligibleAmount($application);
        } elseif (isset($ruleConfig['power_savings'])) {
            // Power saving policy
            $eligibleAmount = $this->powerSavingEligibleAmount($application);
        } elseif (isset($ruleConfig['bureau_surrogate'])) {
            // Bureau surrogate policy
            $eligibleAmount = $this->bureauSurrogateEligibleAmount($application);
        } else {
            // Regular policy
            $eligibleAmount = $this->regularPolicyEligibleAmount($application);
        }

        // add insurance to the eligible amount and then calculate the net loan amount
        $emiController = new EMICalculation(new Request());
        if($emiController->isLoanAmountAddedInLoanAmount($applicationId)){
            $proforma = DB::selectOne("select insurance_premium_amount from pro_forma_invoice where application_id = ?", [$applicationId]);
            $eligibleAmount = $eligibleAmount + (isset($proforma) && !empty($proforma->insurance_premium_amount) ? $proforma->insurance_premium_amount : 0);
        }

        $autoBre = new \App\Http\Controllers\Admin\autoBRE();
        return $autoBre->calculateNetLoanAmount($eligibleAmount, $application->offer);
    }

    /**
     * Eligible amount for a regular policy.
     *
     * If the application's EMI calculation was approved on minimum tenure
     * (table_of_offers_logic = minimum_tenure), the applicant is eligible for the full
     * applied loan amount. Otherwise it falls back to the eligible_amount stored against
     * the application's current offer in the loan_decision_table (0 when not found).
     *
     * @param  \App\ConsumerApplication  $application
     * @return float
     */
    protected function regularPolicyEligibleAmount($application)
    {
        $emiCalculation = ApplicationEMICalculation::where('application_id', $application->id)
            ->orderByDesc('id')
            ->first(['table_of_offers_logic']);

        if ($emiCalculation && $emiCalculation->table_of_offers_logic === 'minimum_tenure') {
            return $application->loan_amount;
        }

        // Fallback: eligible amount stored for the current offer in the decision table.
        $decision = DecisionTable::where('application_id', $application->id)
            ->where('offer_id', $application->offer_id)
            ->whereNull('deleted_at')
            ->orderByDesc('created_at')
            ->first(['eligible_amount']);

        return $decision->eligible_amount ?? 0;
    }

    /**
     * Eligible amount for a power saving policy.
     *
     * Looks at the previously stored POWER-SAVING decisions in loan_decision_approved_amount
     * for this application and picks the calculated_approved_amount closest to (but not
     * exceeding) the application's applied loan amount. If no such record exists, it falls
     * back to calculating the power saving amount.
     *
     * @param  \App\ConsumerApplication  $application
     * @return float
     */
    protected function powerSavingEligibleAmount($application)
    {
        $closestRecord = LoanDecisionApprovedAmount::where('application_id', $application->id)
            ->where('policy_type', 'POWER-SAVING')
            ->where('calculated_approved_amount', '<=', $application->loan_amount)
            ->whereNull('deleted_at')
            ->orderByDesc('calculated_approved_amount')
            ->first(['calculated_approved_amount', 'emi_amount']);

        if (!is_null($closestRecord)) {
            if (isset($closestRecord->emi_amount) && $closestRecord->emi_amount <= $application->emi->emi) {
                return $closestRecord->calculated_approved_amount;
            } else {
                return $application->loan_amount;
            }
        }

        // No stored decision found - calculate the power saving amount.
        return $this->calculatePowerSavingAmount($application);
    }

    public function bankingSurrogateEligibleAmount($application) {
        $closestRecord = LoanDecisionApprovedAmount::where('application_id', $application->id)
            ->where('policy_type', 'BANKING-ABB')
            ->where('calculated_approved_amount', '<=', $application->loan_amount)
            ->whereNull('deleted_at')
            ->orderByDesc('calculated_approved_amount')
            ->first(['calculated_approved_amount', 'emi_amount']);

        if (!is_null($closestRecord)) {
            if (isset($closestRecord->emi_amount) && $closestRecord->emi_amount <= $application->emi->emi) {
                return $closestRecord->calculated_approved_amount;
            } else {
                if ($application->emi->table_of_offers_logic === 'banking_abb') {
                    return $application->loan_amount;
                }
                return 0;
            }
        }
        return $this->calculateBankingSurrogateAmount($application);
    }

    /**
     * Eligible amount for a bureau surrogate policy.
     *
     * Looks up the application's current offer in the loan_decision_table and returns the
     * bureau_surrogate_limit_amount stored against that offer. Returns 0 when no matching
     * decision row (or limit) is found.
     *
     * @param  \App\ConsumerApplication  $application
     * @return float
     */
    protected function bureauSurrogateEligibleAmount($application)
    {
        $decision = DecisionTable::where('application_id', $application->id)
            ->where('offer_id', $application->offer_id)
            ->whereNull('deleted_at')
            ->orderByDesc('bureau_surrogate_limit_amount')
            ->first(['bureau_surrogate_limit_amount']);

        return $decision->bureau_surrogate_limit_amount ?? 0;
    }

    /**
     * Calculate the power saving eligible amount when no stored decision is available.
     *
     * The custom EMI is derived using the same logic as the power saving auto-decision
     * flow (GradeController::fetchCustomEMIAmount). That EMI is then run through the EMI
     * calculation to back-calculate the eligible loan amount.
     *
     * @param  \App\ConsumerApplication  $application
     * @return float
     */
    protected function calculatePowerSavingAmount($application)
    {
        $customEMIAmount = (new GradeController(new Request()))->fetchCustomEMIAmount($application);

        // A 0 custom EMI means the existing EMI already qualifies - eligible for the full applied amount.
        if (is_numeric($customEMIAmount) && $customEMIAmount == 0) {
            return $application->loan_amount;
        }

        if (empty($customEMIAmount)) {
            return 0;
        }

        return $this->getEligibleLoanAmountForEMI($application, $customEMIAmount);
    }

    /**
     * Back-calculate the eligible loan amount for a given EMI using the EMI calculation.
     *
     * The application's own offer (offer_id) is passed as merchant_offer_id so the eligible
     * amount is computed against the applied offer.
     *
     * @param  \App\ConsumerApplication  $application
     * @param  float                     $emiAmount
     * @return float
     */
    protected function getEligibleLoanAmountForEMI($application, $emiAmount)
    {
        $request = new Request();
        $request->attributes->add([
            'merchant_id'       => $application->merchant_id,
            'category_id'       => $application->purpose,
            'emi_amount'        => $emiAmount,
            'application_id'    => $application->id,
            'merchant_offer_id' => $application->offer_id,
        ]);

        $emiDetails = (new EMICalculation($request))->prepareCalculation()->getData();

        return $emiDetails->loan_amount ?? 0;
    }

    protected function calculateBankingSurrogateAmount($application)
    {
        $customEMIAmount = (new GradeController(new Request()))->fetchCustomEMIAmountForBanking($application);

        // A 0 custom EMI means the existing EMI already qualifies - eligible for the full applied amount.
        if (is_numeric($customEMIAmount) && $customEMIAmount == 0) {
            return $application->loan_amount;
        }

        if (empty($customEMIAmount)) {
            return 0;
        }
        return $this->getEligibleLoanAmountForEMI($application, $customEMIAmount);
    }
}