<?php

namespace App;

use App\Http\Controllers\Admin\ConsumerApplicationController;
use App\Http\Controllers\MConnector\MerchantApiIntegration;
use App\Logging\CloudWatchLoggerFactory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\ConsentController;
use App\Http\Controllers\JsonBREController;

class PSUService
{
    private $payload;
    /**
     * @var Request
     */
    private $request;

    private $runningBRE = false;

    private $cwFactory = null;

    private static $APPLICANT = 1;
    private static $CO_APPLICANT = 2;
    /**
     * @var GradeController
     */
    private $gradeController = null;
    /**
     * @var string
     */
    public $error = null;
    /**
     * @var \Illuminate\Http\JsonResponse
     */
    private $bre_response = null;
    private $applicant_type = null;
    private $message = '';
    /**
     * @var string|void
     */
    private $request_id = '';
    private $finalResponse = null;
    private $finalPolicyWiseFailureReason = [];
    private $finalPolicyWiseStatus = [];
    private $get_sse_psu_data_input_json = null;
    private $webookMessage = [];

    public $applicationController;
    public $applicationDetails;
    private $isCoapplicant = false;
    /**
     * @var mixed
     */
    private $event = null;

    public function __construct(Request $request){
        $this->request = $request;
        $this->event = $request->get('event');
        $this->cwFactory = new CloudWatchLoggerFactory();
        $this->applicationController = new ConsumerApplicationController();
        $this->applicationDetails = DB::selectOne("SELECT id, merchant_id, co_applicant_name, loan_amount from consumer_application WHERE id = ?", [$this->request->get('id')]);
        $this->isCoapplicant = !empty($this->applicationDetails->co_applicant_name);
    }

    public function init(){
        (new ConsentController())->sendConsentMessage($this->request->get('id'), 1);
        if ($this->isCoapplicant){
            (new ConsentController())->sendConsentMessage($this->request->get('id'), 2);
        }
        $this->logger("PSU BRE Started");
        $this->request_id = (new MerchantApiIntegration($this->request))->guidv4();
        $this->logger("Running For App Id: ".$this->request->get('id').', RequestId: '.$this->request_id);
        try {
            $this->runBureauService();

            $this->finalBREResponse();
            return $this->finalPolicyWiseFailureReason;
        }catch (\Exception $e){
            $this->logger(["Exception Found : ", $e->getMessage(), $e->getLine(), $e->getFile()], true);
        }
    }

    private function runBureauService()
    {
        $this->applicationController->isEquifaxEnabled = false;

        $this->logger("Running Applicant Bureau");
        $this->generateApplicantBureau($this->applicationController, self::$APPLICANT)
            ->updateFOIR()
            ->runBREService();


        if (!$this->runningBRE){
            return;
        }

        $this->logger("Starting Co-Applicant Bureau Fetch");

        if ($this->isCoapplicant) {
            $this->logger("Running Co-Applicant Bureau");
            $this->generateApplicantBureau($this->applicationController, self::$CO_APPLICANT)
                ->updateFOIR()
                ->runBREService();
        }else{
            $this->logger("No Co-Applicant Found. Aborted", true);
        }
    }


    private function generateApplicantBureau($applicationController, $applicant_type){
        $start = microtime(true);
        $response = $applicationController->generateSoftBureauReport($this->request->get('id'), '', $applicant_type, false);
        $end = microtime(true);
        $this->logger("Bureau Response: - ". json_encode($response->getData()).', '.$this->getProcessTime($end - $start));
        if (isset($response->getData()->status) && $response->getData()->status == 'OK') {
            $this->logger("Bureau Status: Type - ". $applicant_type);
            $this->runningBRE = true;
            $this->applicant_type = $applicant_type;
        }else if(isset($response->getData()->message)){
            $this->runningBRE = false;
            $this->error = $response->getData()->message;
            $this->logger("Bureau Error: ". $this->error, true);
            //TODO: Share Bureau Failure Webhook
            $id = $this->request->get('id');
            $this->message = 'Soft Bureau Failed';
            $payload = $this->getWebhookPayload($id);
            $payload['applicant_type'] = $applicant_type;
            $this->applicationController->webhook($id, ['type' => 'JSON-BRE','status' => null, 'data' => $payload]);
        }

        return $this;
    }

    private function getPolicyPayload($policy)
    {

        $inputData = json_decode(
            $this->get_sse_psu_data_input_json(),
            true
        );

        $basePayload = [
            'identifier'     => $this->request->get('id'),
            'applicant_type' => $this->applicant_type,
            'rule_name'      => 'sse_psu_bre_rule',
        ];

        $policyValues = $inputData[$policy];

        /*$policyValues = $policy === 'HDFC'
            ? [
                'credit_score'       => 720,
                'foir'               => 50,
                'age'                => 45,
                'ageAtLastEMI'       => 55,
                'guarantor'          => 1,
                'no_owned_house_check'=> 1,
            ]
            : [
                'credit_score'       => 560,
                'foir'               => 45,
                'age'                => 40,
                'ageAtLastEMI'       => 50,
                'guarantor'          => 1,
                'no_owned_house_check'=> 0,
            ];*/

        return array_merge($basePayload, $policyValues);
    }

    private function finalBREResponse(){
        $this->prepareDataForWrapper();
        $id = $this->request->get('id');
        if ($this->isApproved()) {
            $this->logger("BRE Approval Status: Passed");
            $payload = $this->getWebhookPayload($id, 'Approved');
            $this->applicationController->webhook($id, ['type' => 'JSON-BRE','status' => 'Approved', 'data' => $payload]);
        } elseif ($this->isRejected()) {
            $this->logger("BRE Approval Status: Failed", true);
            $payload = $this->getWebhookPayload($id, 'Rejected');
            $this->applicationController->webhook($id, ['type' => 'JSON-BRE','status' => 'Rejected', 'data' => $payload]);
        } elseif ($this->manualUnderWriting()) {
            // TODO: Manual Underwriting logic
            $this->logger("BRE - Sent for manual Underwriting");
        }
    }

    private function prepareBREServiceAPIConfig()
    {
        $this->logger("Preparing Payload For BRE");
        $this->jsonBRE = new JsonBREController();

        $filePath = _env('SSE_PSU_JSON_BRE_EXHAUSTIVE_JSON');
        $exhaustiveCriteria = json_decode(
            $this->jsonBRE->get_exhaustive_criteria_json($filePath, $this->request->get('id')),
            true
        );

        $this->logger("SSE PSU Input Data Fetched: ". ($exhaustiveCriteria ? 'Passed' : 'Failed'));

        $policies = ['SBI', 'OTHER_BANKS'];

        $breApiPayload = ['grade' => ['rules' => []], 'identifier' => $this->request->get('id')];

        foreach ($policies as $policyName) {
            $ruleList = $this->create_rule_list($exhaustiveCriteria);

            $breApiPayload['grade']['rules'][$policyName] = [
                'payload' => $this->getPolicyPayload($policyName),
                'rule'    => $ruleList ?: []
            ];
        }

        $this->payload = $breApiPayload;
        $this->logger("SSE PSU Data Payload Prepared");
        return $this;
    }


    private function create_rule_list($exhaustive_criteria_json){

        $rule_list = [];

        $applicant_rules = ['credit_score', 'foir', 'ageAtLastEMI', 'guarantor', 'pan_match', 'aadhaar_match', 'e_bill_match', 'property_tax_match'];

        if ($this->applicationDetails->loan_amount < 200000) {
            unset($applicant_rules[array_search('foir', $applicant_rules)]);
        }
        foreach ($exhaustive_criteria_json as $value) {
            if ($this->applicant_type == 1 && in_array($value['key'], $applicant_rules)){
                $rule_list[] = $value;
            }else if ($this->applicant_type == 2 && !in_array($value['key'], $applicant_rules)){
                $rule_list[] = $value;
            }
        }
        return $rule_list;
    }

    private function runBREService()
    {
        $this->logger("BRE Service Started");

        if (!$this->runningBRE) {
            $this->logger("BRE Service Status: False", true);
            return;
        }

        $this->prepareBREServiceAPIConfig()->runBRE();

        if ($this->error) {
            $this->logger("BRE Error: '.$this->error.' BRE Stopped!", true);
        }

        $this->runningBRE = false;
    }

    public function prepareDataForWrapper() {
        if (!$this->bre_response) {
            $this->logger('No BRE Response!!', true);
            return;
        }

        $this->logger("Preparing For Final BRE Response");

        // Step 1: Normalize response into structured array
        $response = [];
        foreach ($this->bre_response as $applicant_type => $policies) {
            $policies_data = json_decode($policies->res, true);
            foreach ($policies_data as $policy_name => $policy) {
                if (is_array($policy)) {
                    foreach ($policy['rules'] as $rule) {
                        $response[$policy_name][$applicant_type][$rule['key']] = [
                            'status' => $rule['result'],
                            'reason' => $rule['reason'] ?? ''
                        ];
                    }
                }
            }
        }

        $responseData = [];

        // Step 2: Define handler maps
        $handlers = [
            'credit_score'   => 'getFinalCreditScoreResponse',
            'foir'           => 'getFinalFOIRResponse',
            'ageAtLastEMI'   => 'getFinalAgeAtLastEMIResponse',
            'guarantor'      => 'getFinalGuarantorResponse',
            'pan_match'      => 'getPANMatchResponse',
            'aadhaar_match'  => 'getAadharMatchResponse',
            'bill_match'     => 'getBillMatchResponse',
        ];

        $optionalHandlers = [
            'co_app_age'          => 'getFinalAgeResponse',
            'co_app_pan_match'    => 'getCoAppPANMatchResponse',
            'co_app_aadhaar_match'=> 'getCoAppAadharMatchResponse',
        ];

        if ($this->applicationDetails->loan_amount < 200000) {
            unset($handlers['foir']);
        }

        // Step 3: Run all handlers dynamically
        foreach ($response as $policy_name => $policy) {
            foreach ($handlers as $key => $method) {
                $responseData[$policy_name][$key] = $this->$method($policy, $policy_name);
            }

            foreach ($optionalHandlers as $key => $method) {
                if (isset($policy[2][$key])) {
                    $responseData[$policy_name][$key] = $this->$method($policy, $policy_name);
                }
            }
        }

        // Step 4: Evaluate policy-level status
        foreach ($responseData as $policy_name => $policy) {
            $isPolicyLevelPass = !in_array(false, $policy, true);
            $this->logger(['Checking for policy: ' . $policy_name, $policy]);
            $this->logger(['Policy Name: ' . $policy_name . ', Status', $isPolicyLevelPass]);
            $this->finalPolicyWiseStatus[$policy_name] = $isPolicyLevelPass;
        }

        // Step 5: Overall response
        $this->finalResponse = in_array(true, $this->finalPolicyWiseStatus, true);

        $this->logger("BRE Failed Rules");
        $this->logger($this->finalPolicyWiseFailureReason);
        $this->logger("BRE Webhook Message");
        $this->logger($this->webookMessage);
        $this->logger("Final BRE Response Ended");

        return $this->finalResponse;
    }

    private function getFinalCreditScoreResponse($policy, $policy_name) {
        $isSingleApplicant = (count($policy) == 1);

        // Single applicant case
        if ($isSingleApplicant) {
            $status = $policy[1]['credit_score']['status'] ?? false;
            if (!$status) {
                $this->finalPolicyWiseFailureReason[$policy_name][] = [
                    'failure_reason'          => 'Applicant Credit Score Condition Failed',
                    'failure_details' => $policy[1]['credit_score']['reason'] ?? 'Unknown reason'
                ];
            }
            return $status;
        }

        // Multiple applicant case
        $applicantScore   = $policy[1]['credit_score']['status'] ?? false;
        $coApplicantScore = $policy[2]['co_app_credit_score']['status'] ?? false;

        // If applicant passes, always return true (co-applicant failure doesn’t block)
        if ($applicantScore && $coApplicantScore) {
            return true;
        }

        if(!$applicantScore) {
            $this->finalPolicyWiseFailureReason[$policy_name][] = [
                'failure_reason' => 'Applicant Credit Score Condition Failed',
                'failure_details' => $policy[1]['credit_score']['reason'] ?? 'Unknown reason'
            ];
        }

        if (!$coApplicantScore){
            $this->finalPolicyWiseFailureReason[$policy_name][] = [
                'failure_reason'            => 'Co-Applicant Credit Score Condition Failed',
                'failure_details' => $policy[2]['co_app_credit_score']['reason'] ?? 'Unknown reason'
            ];
        }
        return false;
    }

    private function getFinalFOIRResponse($policy, $policy_name) {
        $isSingleApplicant = (count($policy) == 1);

        // Single applicant case
        if ($isSingleApplicant) {
            $status = $policy[1]['foir']['status'] ?? false;
            if (!$status) {
                $this->finalPolicyWiseFailureReason[$policy_name][] = [
                    'failure_reason'         => 'Applicant FOIR Condition Failed',
                    'failure_details'=> $policy[1]['foir']['reason'] ?? 'Unknown reason'
                ];
            }
            return $status;
        }

        // Multiple applicant case
        $applicantFoir     = $policy[1]['foir']['status'] ?? false;
        $coApplicantFoir   = $policy[2]['co_app_foir']['status'] ?? false;

        // If applicant is true, always return true (co-applicant failure doesn’t block)
        if ($applicantFoir && $coApplicantFoir) {
            return true;
        }
        if ($applicantFoir || $coApplicantFoir) {
            return true;
        }

        if(!$applicantFoir) {
            $this->finalPolicyWiseFailureReason[$policy_name][] = [
                'failure_reason' => 'Applicant FOIR Condition Failed',
                'failure_details' => $policy[1]['foir']['reason'] ?? 'Unknown reason'
            ];
        }

        if(!$coApplicantFoir) {
            $this->finalPolicyWiseFailureReason[$policy_name][] = [
                'failure_reason' => 'Co-Applicant FOIR Condition Failed',
                'failure_details' => $policy[2]['co_app_foir']['reason'] ?? 'Unknown reason'
            ];
        }
        return false;
    }

    private function evaluatePolicyCondition($policy, $policy_name, $index, $key, $failureReason, $applicantKey) {
        $status = $policy[$index][$key]['status'] ?? false;
        if (!$status) {
            $reason = $policy[$index][$key]['reason'] ?? 'Unknown reason';
            $this->finalPolicyWiseFailureReason[$policy_name][] = [
                'failure_reason' => $failureReason,
                $applicantKey => $reason
            ];
        }
        return $status;
    }

    private function getFinalAgeAtLastEMIResponse($policy, $policy_name){
        return $this->evaluatePolicyCondition(
            $policy,
            $policy_name,
            1,
            'ageAtLastEMI',
            'Applicant age at Last EMI Condition Failed',
            'failure_details'
        );
    }

    private function getFinalAgeResponse($policy, $policy_name){
        return $this->evaluatePolicyCondition(
            $policy,
            $policy_name,
            2,
            'co_app_age',
            'Co-Applicant Age Condition Failed',
            'failure_details'
        );
    }

    private function getFinalGuarantorResponse($policy, $policy_name){
        $return = $this->evaluatePolicyCondition(
            $policy,
            $policy_name,
            1,
            'guarantor',
            'Applicant Guarantor Condition Failed',
            'failure_details'
        );

        if (!$return) { //Disha to check
            $this->webookMessage[$policy_name][] = 'Guarantor Required as it does not have active loan';
        }
    }

    private function getCoAppPANMatchResponse($policy, $policy_name){
        return $this->evaluatePolicyCondition(
            $policy,
            $policy_name,
            2,
            'co_app_pan_match',
            'Co-Applicant PAN Match Failed',
            'failure_details'
        );
    }

    private function getCoAppAadharMatchResponse($policy, $policy_name){
        return $this->evaluatePolicyCondition(
            $policy,
            $policy_name,
            2,
            'co_app_aadhaar_match',
            'Co-Applicant Aadhaar Match Failed',
            'failure_details'
        );
    }

    private function getPANMatchResponse($policy, $policy_name){
        return $this->evaluatePolicyCondition(
            $policy,
            $policy_name,
            1,
            'pan_match',
            'Applicant PAN Match Failed',
            'failure_details'
        );
    }

    private function getAadharMatchResponse($policy, $policy_name){
        return $this->evaluatePolicyCondition(
            $policy,
            $policy_name,
            1,
            'aadhaar_match',
            'Applicant Aadhaar Match Failed',
            'failure_details'
        );
    }

    private function getBillMatchResponse($policy, $policy_name){
        $isSingleApplicant = (count($policy) == 1);

        // Single applicant case
        if ($isSingleApplicant) {
            $e_bill_match = $this->evaluatePolicyCondition(
                $policy,
                $policy_name,
                1,
                'e_bill_match',
                'Applicant eBILL Name Match Failed',
                'failure_details'
            );

            if ($e_bill_match)
                return $e_bill_match;

            $property_match = $this->evaluatePolicyCondition(
                $policy,
                $policy_name,
                1,
                'property_tax_match',
                'Applicant Property Tax Name Match Failed',
                'failure_details'
            );

            if (!$property_match){
                $this->webookMessage[$policy_name][] = 'Application failed as the E-bill owner name is not there on electricity bill and property tax receipt';
            }

            return $property_match;

        }else{
            $return = $this->evaluatePolicyCondition(
                $policy,
                $policy_name,
                1,
                'e_bill_match',
                'Applicant eBILL Name Match Failed',
                'failure_details'
            );

            if (!$return){
                $this->webookMessage[$policy_name][] = 'Application failed as the E-bill owner name is not there on electricity bill';
            }
        }

        return $return;
    }

    private function runBRE()
    {
        try {
            $start = microtime(true);
            $this->logger("Running JSON BRE Engine...");
            $response = $this->jsonBRE->runAutoDecisionBRE($this->payload);
            $this->bre_response[$this->applicant_type] = $response->getData();
            $end = microtime(true);
            $this->logger("JSON BRE Process Completed, ".$this->getProcessTime($end - $start).", Status: ". (!empty($this->bre_response[$this->applicant_type]->err) ? $this->bre_response[$this->applicant_type]->err : $this->bre_response[$this->applicant_type]->code));
            $this->jsonBRE->store_jsonbre_response($response, '');
            $this->logger("JSON BRE Response Stored");
        } catch (\Exception $e) {
            $this->logger([
                'title'          => 'BRE_API run Failed',
                'application_id' => $this->request->get('id'),
                'req_id'         => $this->request_id
            ], true);
            $this->error = 'BRE_API run Failed: ' . $e->getMessage();
        }
    }

    private function logger($data, $is_error = false){
        if(!_env('APP_CONNECTION')) {
            $message = is_array($data) ? json_encode($data, JSON_PRETTY_PRINT) : $data;
            if ($is_error){
                $this->event->error($message);
            }else{
                $this->event->info($message);
            }
        }
        $this->cwFactory->logger(json_encode(['data' => is_array($data) ? $data : [$data], 'application_id' => $this->request->get('id')]));
    }

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

    private function updateFOIR(){
        if ($this->runningBRE) {
            $start = microtime(true);
            $this->logger("Updating Credit Report: FOIR");
            $this->applicationController->getScoreCard(new Request(['application_id' => $this->request->get('id')]));
            $foir = CreditReport::where('application_id', $this->request->get('id'))->value('foirNAIncludingCFEMI');
            $end = microtime(true);
            $this->logger("Credit Report: FOIR Updated, FOIR: " . $foir);
            $this->logger("Credit Report: FOIR Calculated " . $this->getProcessTime($end - $start));
        }
        return $this;
    }

    private function getProcessTime($executionTime){
        return "Execution time: " . number_format($executionTime * 1000, 2) . " ms (" . number_format($executionTime, 4) . " sec)";
    }

    private function isApproved()
    {
        return $this->finalResponse;
    }

    private function isRejected()
    {
        return is_bool($this->finalResponse) && $this->finalResponse == false;
    }

    private function manualUnderWriting(): bool
    {
        return true;
    }

    public function getWebhookPayload($id, $result=null){
        $payload = [
            'application_id' => $this->applicationDetails->id,
            'merchant_id' => $this->applicationDetails->merchant_id,
            'bre_execution' => !empty($this->finalPolicyWiseStatus) ? true : false,
        ];
        if ($this->message != '') {
            $payload['message'] = $this->message;
            if (isset($this->error)) { 
                $payload['message'] = $this->message ."-". $this->error;
            }
        }
        $payload['policy_wise_status'] = $this->finalPolicyWiseStatus;
        $payload['policy_wise_failure_reasons'] = $this->finalPolicyWiseFailureReason;
        return $payload;
    }
}
