<?php

namespace App\Modules\Admin\Financials;

use App\Models\ConxOutboundPayment;
use App\Models\OutboundBillingRequests;
use App\Events\Company\InvoiceUpdateEvent;
use App\Http\Resources\Admin\ConxOutboundPayment as ConxOutboundPaymentResource;
use App\Http\Resources\Admin\ConxOutboundPaymentDetails as ConxOutboundPaymentDetailsResource;
use App\Traits\SearchTraits;
use App\Traits\SortingTraits;
use App\Traits\ValidatorTraits;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Log;
use App\Models\PaymentAttempt;
use App\Models\LoanBorrowList;
use App\Services\Payments\PayoutService;
use App\Models\PaymentProfile;
use Exception;
class OutboundPayment
{
    use SortingTraits, SearchTraits, ValidatorTraits;
    protected PayoutService $payoutService;

    public function __construct(PayoutService $payoutService)
    {
        $this->payoutService = $payoutService;
    }

    public function getData($payload, $id)
    {
        // sorting
        $sortField = $this->sortField($payload, 'transaction_date'); // transaction_date = default column to search
        $sortOrder = $this->sortOrder($payload, 'desc');

        // searching
        $invoiceNoSearch = $this->searchField($payload->invoice_no);

        $transaction = ConxOutboundPaymentResource::collection(
            ConxOutboundPayment::select(
                'conx_outbound_payments.id',
                'conx_outbound_payments.created_at as transaction_date',
                'conx_outbound_payments.invoice_no',
                'conx_outbound_payments.amount')
                ->where('conx_outbound_payments.outbound_billing_request_id', $id)
                ->when(!empty($invoiceNoSearch), function ($query) use ($invoiceNoSearch) {
                    return $query->where('conx_outbound_payments.invoice_no', 'LIKE', $invoiceNoSearch . '%');
                })
                ->orderBy($sortField, $sortOrder)
                ->paginate(10)
        )->response()->setStatusCode(201);

        return $transaction;
    }

public function _savePaymentData(int $billing_request_id, int $amount_dec, bool $simulate = true): array
{
    DB::beginTransaction();

    try {
        $id = $billing_request_id;
        $amount = $amount_dec / 100; // remove Stripe cent handling

        Log::info('OutboundPayment: _savePaymentData started', [
            'amount' => $amount,
            'simulate' => $simulate,
            'billing_request_id' => $id,
        ]);


        // 1. Save payment record
        $savePaymentTransaction = $this->savePayment($id, $amount, null);
        if (!$savePaymentTransaction) {
            throw new \Exception('Failed to save payment record');
        }
        

        // 2. Update balance and status
        if (!$this->updateOutstandingBalanceAndLoanerStatus($id, $amount)) {
            throw new \Exception('Failed to update outstanding balance');
        }

        // 3. Get payment id
        $paymentId = $this->getNewPaymentPrimaryId($savePaymentTransaction);

        // 4. Commit or simulate
        if ($simulate) {
            DB::rollBack();
            Log::info('OutboundPayment: _savePaymentData simulation completed. All DB changes rolled back.', [
                'simulate' => $simulate,
                'billing_request_id' => $id,
            ]);
        } else {
            DB::commit();
            Log::info('OutboundPayment: _savePaymentData committed successfully. Payment Complete', [
                'simulate' => $simulate,
                'billing_request_id' => $id,
            ]);
        }

        return [
            'success' => true, 
            'payment_id' => $paymentId 
        ];

    } catch (\Exception $e) {
        DB::rollBack();
        Log::error('OutboundPayment: _savePaymentData failed', [
            'simulate' => $simulate,
            'billing_request_id' => $id,
            'error' => $e->getMessage(),
        ]);
        return [
            'success' => false, 
            'payment_id' => null,
        ];
    }
}


    public function savePaymentData($payload, $id)
    {
        try {
            // Step 0: Validate payload
            $rules = [
                'payment_type' => 'required|in:Partial,Full-Payment',
                'amount' => 'required|numeric|min:1',
            ];
            if ($validation = $this->validateRequest($payload, $rules)) {
                return $validation;
            }

            // Step 1: Fetch loan data
            $loan = LoanBorrowList::where('invoice_no', $payload->billing_invoice)
                ->where('id', $id)
                ->first();

            if (!$loan) {
                return response()->json([
                    'error' => 'Loan data not found for invoice ' . $payload->billing_invoice
                ], 404);
            }

            $amountInCents = $payload->amount * 100;
            $companyId = $loan->loaner_company_id;


            // Step 2: Verify payment profile and connected account
            $profile = PaymentProfile::where('company_id', $companyId)
                ->where('provider', 'stripe')
                ->first();

            if (!$profile) {
                throw new Exception('No payment profile found.');
            }

            if( !$profile->provider_account_id){
                throw new Exception('Loaner Stripe connected account is missing. Cannot initiate transfer');
            }

            // Step 3: Simulate payment application (to validate)
            $simulation = $this->_savePaymentData($id, $amountInCents, true);

            if (!$simulation['success']) {
                return response()->json([
                    'error' => "Payment validation failed. Your card has NOT been charged."
                ], 409);
            }

            // Step 4: Prepare metadata for Stripe transfer
            $metadata = [
                'type' => 'Loaner Invoice',
                'payment_type' => $payload->payment_type,
                'billing_request_id' => $id,
                'billing_invoice' => $payload->billing_invoice,
                'company_id' => $companyId,
            ];
            Log::info('OutboundPayment: provider_account_id' . $profile->provider_account_id);
            // Step 5: Transfer funds to the connected account
            $transfer = $this->payoutService->transferToConnectedAccount([
                'amount' => $amountInCents,
                'currency' => 'usd',
                'destination' => $profile->provider_account_id,
                'description' => 'Loan disbursement for invoice ' . $payload->billing_invoice,
                'metadata' => $metadata,
            ]);

            Log::info('OutboundPayment: Stripe transfer initiated', [
                'amount' => $payload->amount,
                'transfer_id' => $transfer['id'] ?? null,
                'status' => $transfer['status'] ?? null,
            ]);

            // Step 5: Finalize payment record
            $finalData = $this->_savePaymentData($id, $amountInCents, false);

            // Step 6: Trigger invoice updated event
            event(new InvoiceUpdateEvent($finalData['payment_id'], "outbound_payment", $payload->payment_type));

            return $this->fetchNewPaymentData($finalData['payment_id']);

        } 
        catch (\Stripe\Exception\CardException $e) {
            $error = $e->getError()->message ?? $e->getMessage();
            Log::warning('OutboundPayment: Stripe CardException', ['error' => $error]);
            return response()->json(['error' => $error], 402);
        } 
        catch (\Stripe\Exception\ApiErrorException $e) {
            $error = $e->getMessage();
            Log::error('OutboundPayment: Stripe API error', ['error' => $error]);
            return response()->json(['error' => $error], 500);
        } 
        catch (\Exception $e) {
            $error = $e->getMessage();
            Log::error('OutboundPayment: savePaymentData Exception', ['error' => $error]);
            return response()->json(['error' => $error], 500);
        }

    }

/*
    public function savePaymentData($payload, $id)
    {
         return response()->json(['error' => 'testing is working!!!!']);

        // start Transaction
        DB::beginTransaction();

        $paymentType = $payload->payment_type;
        $amount = $payload->amount;

        // validate query params
        $rules = array(
            'payment_type' => 'required|in:Partial,Full-Payment',
            'amount' => 'required',
        );
        $validate = $this->validateRequest($payload, $rules);
        if ($validate) {
            return $validate;
        }

        $savePaymentTransaction = $this->savePayment($id, $amount, $paymentType);
        $paymentId = $this->getNewPaymentPrimaryId($savePaymentTransaction);
        $updateOutstandingBalanceAndLoanerStatusTransaction = $this->updateOutstandingBalanceAndLoanerStatus($id, $amount);

        // if there's an error or queries don't do their job, rollback!
        if (!$savePaymentTransaction ||
            !$updateOutstandingBalanceAndLoanerStatusTransaction) {
            DB::rollBack();
            return response()->json(['error' => 'Server error.']);
        } else {
            DB::commit();
            event(new InvoiceUpdateEvent($transaction->id, "outbound_payment", $paymentType));
            return $this->fetchNewPaymentData($paymentId);
        }
    }
*/
    public function savePayment($id, $amount, $paymentType)
    {
        $dateToday = Carbon::now()->format('Y-m-d-h-i-s');
        $invoiceNo = 'IVN-OP-' . $dateToday . '-' . Str::random(10);
        $worker_data = array(
            'invoice_no' => $invoiceNo,
            'outbound_billing_request_id' => $id,
            'amount' => $amount,
        );
        $transaction = ConxOutboundPayment::create($worker_data);

        return $transaction;
    }

    public function getNewPaymentPrimaryId($payment)
    {
        return $payment->id;
    }

    public function updateOutstandingBalanceAndLoanerStatus($id, $amount)
    {
        $outstandingBalance = $this->fetchOutstandingBalance($id);
        $outstandingBalance = $outstandingBalance - $amount;
        $loanerStatus = 'Partial';
        if ($outstandingBalance == 0) {
            $loanerStatus = 'Paid';
        }
        $form_data = array(
            'outstanding_balance' => $outstandingBalance,
            'loaner_status' => $loanerStatus,
        );
        return OutboundBillingRequests::where('id', $id)->update($form_data);
    }

    public function fetchOutstandingBalance($id)
    {
        $outstandingBalance = OutboundBillingRequests::select('outstanding_balance')
            ->where('id', $id)
            ->get();
        return $outstandingBalance[0]->outstanding_balance;
    }

    public function fetchNewPaymentData($paymentId)
    {
        return ConxOutboundPaymentDetailsResource::collection(
            ConxOutboundPayment::select(
                'conx_outbound_payments.id',
                'conx_outbound_payments.created_at as transaction_date',
                'conx_outbound_payments.invoice_no',
                'conx_outbound_payments.amount')
                ->where('conx_outbound_payments.id', $paymentId)
                ->limit(1)->get()
        )->response()->setStatusCode(201);
    }
}
