<?php

namespace App\Modules\Admin\Financials;

use App\Models\InboundBillingRequests;
use App\Models\OutboundBillingRequests;
use App\Models\ConxInboundPayment;
use App\Http\Resources\Admin\ConxInboundPayment as ConxInboundPaymentResource;
use App\Http\Resources\Admin\ConxInboundPaymentDetails as ConxInboundPaymentDetailsResource;
use App\Traits\SearchTraits;
use App\Traits\SortingTraits;
use App\Traits\ValidatorTraits;
use App\Traits\CreateRealTimeCompanyNotificationTraits;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use App\Services\Payments\PaymentMethodService;
use Illuminate\Support\Facades\Log;
use App\Models\PaymentAttempt;
use Exception;
class InboundPayment
{
    use SortingTraits, SearchTraits, ValidatorTraits, CreateRealTimeCompanyNotificationTraits;

    protected PaymentMethodService $paymentMethodService;

    public function __construct(PaymentMethodService $paymentMethodService)
    {
        $this->paymentMethodService = $paymentMethodService;
    }
    /**
     * Finalize a successful payment attempt.
     *
     * This method applies the actual payment to the billing record using `_savePaymentData`,
     * updates the `payment_id` on the attempt record, and sends a company notification.
     *
     * @param PaymentAttempt $paymentAttempt The payment attempt record to finalize.
     * @return void
     */
    public function finalizeSuccessfulPayment(PaymentAttempt $paymentAttempt): void
    {

        Log::info('InboundPayment: finalizeSuccessfulPayment', [
            'payment_attempt_id' => $paymentAttempt->id,
            'billing_request_id' => $paymentAttempt->billing_request_id,
            'amount' => $paymentAttempt->amount,
        ]);

        // Apply the payment and persist to billing/invoice tables
        $data = $this->_savePaymentData(
            $paymentAttempt->billing_request_id,
            $paymentAttempt->amount,
            false // This is a real commit, not a simulation
        );

        // Save the actual payment_id to the attempt record for tracking
        $paymentAttempt->update([
            'payment_id' => $data['payment_id']
        ]);

        // Prepare notification message
        $metadata = json_decode($paymentAttempt->metadata, true);
        $paymentType = $metadata['payment_type'] === 'Partial' ? 'partially' : 'fully';

        $this->createCompanyNotification([
            'type' => 'Borrow Invoice',
            'company_id' => $paymentAttempt->company_id,
            'link' => '/company/borrow/financials',
            'message' => 'This is to notify you that ConX ' . $paymentType . ' paid the invoice ' . $metadata['billing_invoice'],
        ]);

    }


/**
 * Save payment data and update associated billing records.
 *
 * This method handles the full flow of creating a payment record,
 * updating the outstanding balance, updating loaner billing status,
 * and either committing or simulating the transaction.
 *
 * @param int  $billing_request_id  The ID of the billing request to apply the payment to.
 * @param int  $amount_dec          The payment amount in decimal cents (e.g. 50000 = ₱500.00).
 * @param bool $simulate            If true, the transaction is simulated and rolled back (for testing).
 *
* @return array{
*     success: bool,                 // true if all steps completed without error
*     payment_id: int|null          // The new payment's ID, or null on failure
* }
 *
 * @throws \Exception If any step in the payment processing fails.
 */
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('InboundPayment: _savePaymentData started', [
            'amount' => $amount,
            'simulate' => $simulate,
            'billing_request_id' => $id,
        ]);

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

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

        if (!$this->updateOutboundBillingRequestLoanerStatus($id)) {
            throw new \Exception('Failed to update loaner billing status');
        }

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

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

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

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











/**
 * Handles the initial payment flow:
 * - Validates payload
 * - Simulates saving the payment data to ensure invoice is valid
 * - Creates a Stripe PaymentIntent
 * - Logs a pending PaymentAttempt record
 * 
 * @param \Illuminate\Http\Request $payload The incoming payment request payload
 * @param int $id The billing request ID
 * @return \Illuminate\Http\JsonResponse
 */
public function savePaymentData($payload, $id)
{
    try {
        // Step 0: Validate request payload
        $rules = [
            'payment_type' => 'required|in:Partial,Full-Payment',
            'amount' => 'required|numeric|min:0.01',
        ];
        $validate = $this->validateRequest($payload, $rules);
        if ($validate) return $validate;

        // Step 1: Check if there’s already a pending payment attempt
        $existingAttempt = PaymentAttempt::where('billing_request_id', $id)
            ->where('status', 'pending')
            ->first();

        if ($existingAttempt) {
            return response()->json([
                'error' => "We're still processing your previous payment for this invoice. You won't be charged again. Please refresh the page in a few moments to check the status.",
            ], 409);
        }

        $amount = $payload->amount;
        $companyId = $payload->borrowing_company_id;

        // Step 2: Simulate saving payment data (invoice logic only, no actual DB commit)
        $data = $this->_savePaymentData(
            $id,
            $amount * 100, // Convert to cents
            true // Simulate only — don’t commit yet
        );

        if (!$data['success']) {
            return response()->json([
                'error' => "Failed to validate invoice payment data. Your card has NOT been charged.",
            ], 409);
        }

        // Step 3: Build metadata for Stripe
        $tempMetadata = [
            'type' => 'Borrow Invoice',
            'payment_type' => $payload->payment_type,
            'billing_request_id' => $id,
            'billing_invoice' => $payload->billing_invoice,
            'company_id' => $companyId,
            // No payment_attempt_id yet
        ];

        // Step 4: Trigger off-session Stripe payment
        $intent = $this->paymentMethodService->chargeCustomer(
            $companyId,
            $amount * 100, // Stripe accepts amount in cents
            'Borrow Invoice Payment',
            $tempMetadata
        );
        Log::info('InboundPayment: savePaymentData - Stripe PaymentIntent created', [
            'id' => $intent['id'],
            'status' => $intent['status'],
        ]);

        // Step 5: Save payment attempt to DB

        $attempt = PaymentAttempt::create([
            'provider' => 'stripe',
            'company_id' => $companyId,
            'billing_request_id' => $id,
            'amount' => $amount * 100,
            'status' => 'pending',
            'payment_intent_id' => $intent['id'],
            'metadata' => json_encode($tempMetadata),
        ]);

        Log::info('InboundPayment: savePaymentData - PaymentAttempt::create', [
            'amount' => $amount,
        ]);

        return response()->json([
            'message' => 'Payment started, awaiting Stripe confirmation.',
            'payment_attempt_id' => $attempt->id,
        ], 201);
    } catch (\Stripe\Exception\CardException $e) {
        // Card declined, insufficient funds, etc.
        $error = $e->getError()->message ?? $e->getMessage();
        Log::warning('Stripe CardException', ['error' => $error]);
        return response()->json(['error' => $error], 402); // Payment Required

    } catch (\Stripe\Exception\ApiErrorException $e) {
        // Stripe API errors (network, auth, etc.)
        $error = $e->getMessage();
        Log::error('Stripe API error', ['error' => $error]);
        return response()->json(['error' => $error], 500);

    } catch (\Exception $e) {
        // General fallback
        $error = $e->getMessage();
        Log::error('InboundPayment::savePaymentData exception', ['error' => $error]);
        return response()->json(['error' => $error], 500);
    }
}


    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 = ConxInboundPaymentResource::collection(
            ConxInboundPayment::select(
                'conx_inbound_payments.id',
                'conx_inbound_payments.created_at as transaction_date',
                'conx_inbound_payments.invoice_no',
                'conx_inbound_payments.amount')
                ->where('conx_inbound_payments.inbound_billing_request_id', $id)
                ->when(!empty($invoiceNoSearch), function ($query) use ($invoiceNoSearch) {
                    return $query->where('conx_inbound_payments.invoice_no', 'LIKE', $invoiceNoSearch . '%');
                })
                ->orderBy($sortField, $sortOrder)
                ->paginate(10)
        )->response()->setStatusCode(201);

        return $transaction;
    }

/*
    public function savePaymentData($payload, $id)
    {

        // start transaction
        DB::beginTransaction();

        $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);
        $paymentId = $this->getNewPaymentPrimaryId($savePaymentTransaction);
        $updateOutstandingBalanceTransaction = $this->updateOutstandingBalance($id, $amount);
        $updateOutboundBillingRequestLoanerStatusTransaction = $this->updateOutboundBillingRequestLoanerStatus($id);
        

        // if there's an error or queries don't do their job, rollback!
        if (!$savePaymentTransaction ||
            !$updateOutstandingBalanceTransaction ||
            !$updateOutboundBillingRequestLoanerStatusTransaction) {
            DB::rollBack();
            return response()->json(['error' => 'Server error.']);
        } else {
            DB::commit();
            // Send email here
            $paymentType = ($payload->payment_type) == 'Partial'? 'partially' : 'fully' ;
            $companyNotification = array(
                'type' => 'Borrow Invoice',
                'company_id' => $payload->borrowing_company_id,
                'link' => '/company/borrow/financials',
                'message' => 'This is to notify you that ConX '. $paymentType . ' paid the invoice <b>'. $payload->billing_invoice .'</b>',
            );
            $this->createCompanyNotification($companyNotification);
            return $this->fetchNewPaymentData($paymentId);
        }
    }
*/
    public function savePayment($id, $amount)
    {
        $dateToday = Carbon::now()->format('Y-m-d-h-i-s');
        $invoiceNo = 'IVN-IP-' . $dateToday . '-' . Str::random(10);
        $workerData = array(
            'invoice_no' => $invoiceNo,
            'inbound_billing_request_id' => $id,
            'amount' => $amount,
        );
        return ConxInboundPayment::create($workerData);
    }

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

    public function updateOutstandingBalance($id, $amount)
    {
        $outstandingBalance = $this->fetchOutstandingBalance($id);
        $outstandingBalance = $outstandingBalance - $amount;
        $formData = array(
            'outstanding_balance' => $outstandingBalance,
        );
        return InboundBillingRequests::where('id', $id)->update($formData);
    }

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

    public function updateOutboundBillingRequestLoanerStatus($id)
    {
        $outstandingBalance = $this->fetchOutstandingBalance($id);
        if ($outstandingBalance == 0) {
            $formData = array(
                'conx_status' => 'Paid',
            );
            return OutboundBillingRequests::where('id', $id)->update($formData);
        } else {
            $formData = array(
                'conx_status' => 'Partial',
            );
            return OutboundBillingRequests::where('id', $id)->update($formData);
        }
    }

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