<?php

namespace Tests\Feature;

use App\Models\AccountingEntry;
use App\Models\Branch;
use App\Models\Business;
use App\Models\Customer;
use App\Models\FinancialAccount;
use App\Models\Loan;
use App\Models\LoanPayment;
use App\Models\LoanProduct;
use App\Models\RemotePaymentSubmission;
use App\Models\Role;
use App\Models\User;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;

class RemotePaymentWorkflowTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        Storage::fake('local');
        $this->seed(DatabaseSeeder::class);
    }

    public function test_customer_submission_stays_pending_and_does_not_change_finances(): void
    {
        [, $loan,$account]=$this->fixture('customer-pending');
        $before=$account->current_balance;

        $this->post(route('remote-payments.public.store'),$this->customerPayload($loan,'30000.00','MPE-PENDING-001'))
            ->assertRedirect(route('remote-payments.public.create'))->assertSessionHas('status');

        $submission=RemotePaymentSubmission::firstOrFail();
        $this->assertMatchesRegularExpression('/^RMP-[A-Z0-9]+-\d{4}-\d{6}$/',$submission->submission_number);
        $this->assertSame('pending_verification',$submission->status);
        $this->assertSame('customer',$submission->submitted_by_type);
        $this->assertSame('manual-review',$submission->provider_code);
        $this->assertSame('manual_review_required',$submission->provider_status);
        $this->assertNull($submission->loan_payment_id);
        Storage::disk('local')->assertExists($submission->proof_path);
        $this->assertSame(0,LoanPayment::count());
        $this->assertSame($before,$account->fresh()->current_balance);
        $this->assertSame('114000.00',$loan->fresh()->total_outstanding);
        $this->assertSame(0,AccountingEntry::where('event_type','loan_payment')->count());
        $this->assertDatabaseHas('audit_logs',['action'=>'remote_payment.customer_submitted','branch_id'=>$loan->branch_id]);
    }

    public function test_approval_reuses_payment_engine_and_duplicate_approval_is_blocked(): void
    {
        [, $loan,$account,$cashier]=$this->fixture('approve');
        $this->post(route('remote-payments.public.store'),$this->customerPayload($loan,'30000.00','MPE-APPROVE-001'))->assertRedirect();
        $submission=RemotePaymentSubmission::firstOrFail();

        $this->actingAs($cashier)->post(route('remote-payments.approve',$submission),[
            'financial_account_id'=>$account->id,
            'payment_date'=>now()->format('Y-m-d'),
            'verification_notes'=>'Reference and receipt verified.',
        ])->assertRedirect(route('remote-payments.show',$submission));

        $submission->refresh(); $loan->refresh(); $account->refresh();
        $payment=LoanPayment::firstOrFail();
        $this->assertSame('approved',$submission->status);
        $this->assertSame($payment->id,$submission->loan_payment_id);
        $this->assertSame($cashier->id,$submission->verified_by);
        $this->assertSame('MPE-APPROVE-001',$payment->reference);
        $this->assertSame('mobile_money',$payment->method);
        $this->assertSame('30000.00',$payment->amount);
        $this->assertSame('23000.00',$payment->principal_amount);
        $this->assertSame('5000.00',$payment->interest_amount);
        $this->assertSame('2000.00',$payment->fees_amount);
        $this->assertSame('1030000.00',$account->current_balance);
        $this->assertSame('84000.00',$loan->total_outstanding);
        $this->assertDatabaseHas('customer_statement_entries',['loan_id'=>$loan->id,'related_type'=>LoanPayment::class,'related_id'=>$payment->id,'credit'=>30000,'running_balance'=>84000]);
        $this->assertDatabaseHas('daily_collections',['branch_id'=>$loan->branch_id,'financial_account_id'=>$account->id,'amount'=>30000,'payments_count'=>1]);
        $this->assertDatabaseHas('audit_logs',['action'=>'remote_payment.approved','auditable_id'=>$submission->id]);

        $balance=$account->current_balance;
        $this->actingAs($cashier)->post(route('remote-payments.approve',$submission),[
            'financial_account_id'=>$account->id,'payment_date'=>now()->format('Y-m-d'),
        ])->assertSessionHasErrors('status');
        $this->assertSame(1,LoanPayment::count());
        $this->assertSame($balance,$account->fresh()->current_balance);
    }

    public function test_rejection_is_permanent_and_never_posts_money(): void
    {
        [, $loan,$account,$cashier]=$this->fixture('reject');
        $this->post(route('remote-payments.public.store'),$this->customerPayload($loan,'20000.00','AIR-REJECT-001','airtel_money'))->assertRedirect();
        $submission=RemotePaymentSubmission::firstOrFail(); $before=$account->current_balance;

        $this->actingAs($cashier)->post(route('remote-payments.reject',$submission),['verification_notes'=>'Reference does not match the uploaded receipt.'])
            ->assertRedirect(route('remote-payments.show',$submission));
        $submission->refresh();
        $this->assertSame('rejected',$submission->status);
        $this->assertNull($submission->loan_payment_id);
        $this->assertSame(0,LoanPayment::count());
        $this->assertSame($before,$account->fresh()->current_balance);
        $this->assertSame('114000.00',$loan->fresh()->total_outstanding);

        $this->actingAs($cashier)->post(route('remote-payments.approve',$submission),[
            'financial_account_id'=>$account->id,'payment_date'=>now()->format('Y-m-d'),
        ])->assertSessionHasErrors('status');
        $this->assertSame(0,LoanPayment::count());
    }

    public function test_customer_identity_and_duplicate_transaction_reference_are_enforced(): void
    {
        [, $loan]=$this->fixture('identity');
        $payload=$this->customerPayload($loan,'10000.00','BANK-DUP-001','bank');
        $payload['customer_phone']='0719999999';
        $this->post(route('remote-payments.public.store'),$payload)->assertSessionHasErrors('loan_number');
        $this->assertSame(0,RemotePaymentSubmission::count());

        $this->post(route('remote-payments.public.store'),$this->customerPayload($loan,'10000.00','BANK-DUP-001','bank'))->assertRedirect();
        $this->post(route('remote-payments.public.store'),$this->customerPayload($loan,'5000.00','BANK-DUP-001','bank'))->assertSessionHasErrors('transaction_reference');
        $this->assertSame(1,RemotePaymentSubmission::count());
    }

    public function test_staff_submission_verification_permissions_and_branch_isolation_are_enforced(): void
    {
        [$branch,$loan,$account,$cashier,$officer]=$this->fixture('security');
        $business=$branch->business; $otherBranch=Branch::create(['business_id'=>$business->id,'name'=>'Remote Other','status'=>'active']);
        $otherCashier=$this->staff('cashier',$otherBranch,'remote_other_cashier');

        $this->actingAs($officer)->get(route('remote-payments.create'))->assertOk()->assertSee('data-ui="form-shell"',false);
        $this->actingAs($officer)->post(route('remote-payments.store'),$this->staffPayload($loan,'12000.00','STAFF-REMOTE-001'))->assertRedirect(route('remote-payments.index'));
        $submission=RemotePaymentSubmission::firstOrFail();
        $this->assertSame('staff',$submission->submitted_by_type);
        $this->assertSame($officer->id,$submission->submitted_by_user_id);
        $this->actingAs($officer)->get(route('remote-payments.show',$submission))->assertForbidden();
        $this->actingAs($cashier)->get(route('remote-payments.show',$submission))->assertOk()->assertSee($submission->submission_number)->assertSee('Manual Review Required',false);
        $this->actingAs($cashier)->get(route('remote-payments.proof',$submission))->assertOk();
        $this->actingAs($otherCashier)->get(route('remote-payments.show',$submission))->assertForbidden();
        $this->actingAs($otherCashier)->post(route('remote-payments.approve',$submission),[
            'financial_account_id'=>$account->id,'payment_date'=>now()->format('Y-m-d'),
        ])->assertForbidden();
        $this->assertSame(0,LoanPayment::count());
    }

    public function test_staff_cannot_submit_a_remote_payment_for_another_branch_loan(): void
    {
        [$branch,$loan,,,$officer]=$this->fixture('branch-source');
        $otherBranch=Branch::create(['business_id'=>$branch->business_id,'name'=>'Remote Submission Other','status'=>'active']);
        $otherOfficer=$this->staff('loan-officer',$otherBranch,'remote_other_officer');

        $this->actingAs($otherOfficer)->post(route('remote-payments.store'),$this->staffPayload($loan,'10000.00','STAFF-CROSS-001'))
            ->assertSessionHasErrors('loan_number');
        $this->assertSame(0,RemotePaymentSubmission::count());
        $this->actingAs($officer)->get(route('remote-payments.index'))->assertOk()->assertSee('Verification queue');
    }

    private function fixture(string $tag): array
    {
        $business=Business::firstOrFail();
        $branch=Branch::create(['business_id'=>$business->id,'name'=>'Remote '.$tag,'status'=>'active']);
        $customer=Customer::create(['branch_id'=>$branch->id,'customer_number'=>'CUS-RMT-'.strtoupper(substr(md5($tag),0,8)),'identity_mode'=>'without_nin','first_name'=>'Asha','last_name'=>'Musa','phone'=>'0755'.str_pad((string)$branch->id,6,'0',STR_PAD_LEFT),'status'=>'active']);
        $product=LoanProduct::create(['business_id'=>$business->id,'name'=>'Remote Product '.$tag,'minimum_amount'=>'10000.00','maximum_amount'=>'500000.00','duration'=>2,'duration_unit'=>'months','interest_rate'=>'10.000000','interest_calculation_method'=>'flat','repayment_frequency'=>'monthly','grace_period_days'=>0,'late_payment_penalty_type'=>'fixed','late_payment_penalty_value'=>'0.000000','processing_fee_type'=>'fixed','processing_fee_value'=>'4000.000000','insurance_fee_type'=>'fixed','insurance_fee_value'=>'0.000000','other_charges'=>'0.00','collateral_required'=>false,'guarantor_required'=>false,'status'=>'active']);
        $loan=Loan::create(['branch_id'=>$branch->id,'customer_id'=>$customer->id,'loan_product_id'=>$product->id,'sequence_number'=>1,'sequence_year'=>(int)now()->format('Y'),'loan_number'=>'LN-RMT-'.strtoupper(substr(md5($tag),0,8)),'principal'=>'100000.00','interest'=>'10000.00','processing_fee'=>'4000.00','insurance_fee'=>'0.00','other_charges'=>'0.00','fees'=>'4000.00','total_repayment'=>'114000.00','principal_outstanding'=>'100000.00','interest_outstanding'=>'10000.00','fees_outstanding'=>'4000.00','penalties_outstanding'=>'0.00','total_outstanding'=>'114000.00','total_paid'=>'0.00','purpose'=>'Working capital','schedule_start_date'=>now()->format('Y-m-d'),'status'=>'active','disbursed_at'=>now(),'activated_at'=>now(),'schedule_activated_at'=>now()]);
        foreach([1,2] as $number) $loan->installments()->create(['installment_number'=>$number,'principal'=>'50000.00','interest'=>'5000.00','fees'=>'2000.00','amount_due'=>'57000.00','due_date'=>now()->addMonths($number)->format('Y-m-d'),'amount_paid'=>'0.00','remaining'=>'57000.00','status'=>'upcoming','principal_paid'=>'0.00','interest_paid'=>'0.00','fees_paid'=>'0.00','penalty_due'=>'0.00','penalty_paid'=>'0.00']);
        $account=FinancialAccount::create(['branch_id'=>$branch->id,'name'=>'Remote Till '.$tag,'account_type'=>'mobile_money','opening_balance'=>'1000000.00','current_balance'=>'1000000.00','status'=>'active']);
        $cashier=$this->staff('cashier',$branch,'remote_cashier_'.substr(md5($tag),0,6));
        $officer=$this->staff('loan-officer',$branch,'remote_officer_'.substr(md5($tag),0,6));
        return [$branch,$loan,$account,$cashier,$officer];
    }

    private function staff(string $role, Branch $branch, string $username): User
    {
        $user=User::factory()->create(['name'=>str($username)->replace('_',' ')->title()->toString(),'username'=>$username,'status'=>'active','primary_branch_id'=>$branch->id]);
        $user->roles()->attach(Role::where('name',$role)->firstOrFail());
        $user->branches()->attach($branch->id,['is_primary'=>true]);
        return $user;
    }

    private function customerPayload(Loan $loan, string $amount, string $reference, string $channel='mpesa'): array
    {
        $phone = $this->inBranch((int) $loan->branch_id, fn () => $loan->customer()->value('phone'));

        return [
            'loan_number'=>$loan->loan_number,
            'customer_phone'=>$phone,
            'amount'=>$amount,
            'payment_channel'=>$channel,
            'transaction_reference'=>$reference,
            'proof'=>UploadedFile::fake()->create('receipt.pdf',20,'application/pdf'),
            'notes'=>'Customer remote payment evidence',
        ];
    }

    private function staffPayload(Loan $loan, string $amount, string $reference): array
    {
        return [
            'loan_number'=>$loan->loan_number,
            'amount'=>$amount,
            'payment_channel'=>'mpesa',
            'transaction_reference'=>$reference,
            'proof'=>UploadedFile::fake()->create('staff-proof.pdf',20,'application/pdf'),
            'notes'=>'Submitted by branch staff',
        ];
    }
}
