<?php

namespace Tests\Feature;

use App\Models\Branch;
use App\Models\Business;
use App\Models\Customer;
use App\Models\CustomerStatementEntry;
use App\Models\FinancialAccount;
use App\Models\Loan;
use App\Models\LoanPayment;
use App\Models\LoanPenaltyCharge;
use App\Models\LoanProduct;
use App\Models\User;
use App\Services\PaymentDeadlinePenaltyService;
use Carbon\CarbonImmutable;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class PaymentDeadlinePenaltyWorkflowTest extends TestCase
{
    use RefreshDatabase;

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

    public function test_payment_after_deadline_is_charged_once_and_can_be_collected_with_that_payment(): void
    {
        [$business, $branch, $loan] = $this->deadlineLoan('same-day-late', '2026-08-20');
        $business->update([
            'payment_deadline_penalties_enabled'=>true,
            'deadline_late_penalty_type'=>'fixed',
            'deadline_late_penalty_value'=>'500.000000',
            'deadline_late_penalty_settlement'=>'next_payment',
        ]);
        $loan->update([
            'deadline_late_penalty_type'=>'fixed',
            'deadline_late_penalty_value'=>'500.000000',
            'deadline_late_penalty_settlement'=>'next_payment',
        ]);

        $account = FinancialAccount::create([
            'branch_id'=>$branch->id,'name'=>'Deadline Cash','account_type'=>'cash',
            'opening_balance'=>'0.00','current_balance'=>'0.00','status'=>'active',
        ]);
        $admin = User::where('username','admin')->firstOrFail();

        $this->actingAs($admin)->post(route('loan-payments.store',$loan), [
            'amount'=>'10500.00',
            'date'=>'2026-08-20 18:00:00',
            'method'=>'cash',
            'financial_account_id'=>$account->id,
            'reference'=>'LATE-1800',
        ])->assertRedirect();

        $charge = LoanPenaltyCharge::where('loan_id',$loan->id)->where('penalty_reason','deadline_late')->firstOrFail();
        $payment = LoanPayment::where('loan_id',$loan->id)->firstOrFail();
        $this->assertSame('500.00',(string)$charge->amount);
        $this->assertSame('next_payment',$charge->settlement_mode);
        $this->assertSame('500.00',(string)$payment->penalties_amount);
        $this->assertSame('10000.00',(string)$payment->principal_amount);
        $this->assertSame('completed',$loan->fresh()->status);
        $this->assertSame('10500.00',(string)$account->fresh()->current_balance);
    }

    public function test_on_time_payment_has_no_deadline_penalty_and_late_assessment_is_idempotent(): void
    {
        [$business, , $loan] = $this->deadlineLoan('on-time', '2026-08-20');
        $business->update([
            'payment_deadline_penalties_enabled'=>true,
            'deadline_late_penalty_type'=>'fixed',
            'deadline_late_penalty_value'=>'400.000000',
        ]);
        $loan->update(['deadline_late_penalty_type'=>'fixed','deadline_late_penalty_value'=>'400.000000']);
        $service = app(PaymentDeadlinePenaltyService::class);

        $this->assertSame(0,$service->assessLatePayment($loan,CarbonImmutable::parse('2026-08-20 16:59:00')));
        $this->assertSame(40000,$service->assessLatePayment($loan->fresh(),CarbonImmutable::parse('2026-08-20 18:00:00')));
        $this->assertSame(0,$service->assessLatePayment($loan->fresh(),CarbonImmutable::parse('2026-08-20 19:00:00')));
        $count=$this->inBranch((int)$loan->branch_id, fn () => LoanPenaltyCharge::where('loan_id',$loan->id)->where('penalty_reason','deadline_late')->count());
        $this->assertSame(1,$count);
        $this->assertSame('400.00',(string)$loan->fresh()->penalties_outstanding);
    }

    public function test_skipped_schedule_day_gets_one_fixed_or_percentage_penalty_from_unpaid_schedule_amount(): void
    {
        [$business, , $loan] = $this->deadlineLoan('missed-day', '2026-08-20');
        $business->update([
            'payment_deadline_penalties_enabled'=>true,
            'missed_schedule_penalty_type'=>'percentage',
            'missed_schedule_penalty_value'=>'10.000000',
            'missed_schedule_penalty_settlement'=>'add_to_loan',
        ]);
        $loan->update([
            'missed_schedule_penalty_type'=>'percentage',
            'missed_schedule_penalty_value'=>'10.000000',
            'missed_schedule_penalty_settlement'=>'add_to_loan',
        ]);

        $this->artisan('loans:update-overdue',['--date'=>'2026-08-21'])->assertSuccessful();

        $charge = $this->inBranch((int)$loan->branch_id, fn () => LoanPenaltyCharge::where('loan_id',$loan->id)->where('penalty_reason','missed_schedule')->firstOrFail());
        $this->assertSame('10000.00',(string)$charge->base_amount);
        $this->assertSame('1000.00',(string)$charge->amount);
        $this->assertSame('add_to_loan',$charge->settlement_mode);
        $this->assertSame('1000.00',(string)$loan->fresh()->penalties_outstanding);
        $this->assertSame('11000.00',(string)$loan->fresh()->total_outstanding);

        $this->artisan('loans:update-overdue',['--date'=>'2026-08-22'])->assertSuccessful();
        $count=$this->inBranch((int)$loan->branch_id, fn () => LoanPenaltyCharge::where('loan_id',$loan->id)->where('penalty_reason','missed_schedule')->count());
        $this->assertSame(1,$count);
    }

    public function test_approved_installment_excuse_prevents_both_deadline_and_missed_day_penalties(): void
    {
        [$business, , $loan] = $this->deadlineLoan('excused', '2026-08-20');
        $business->update([
            'payment_deadline_penalties_enabled'=>true,
            'deadline_late_penalty_value'=>'500.000000',
            'missed_schedule_penalty_value'=>'700.000000',
        ]);
        $loan->update([
            'deadline_late_penalty_type'=>'fixed','deadline_late_penalty_value'=>'500.000000',
            'missed_schedule_penalty_type'=>'fixed','missed_schedule_penalty_value'=>'700.000000',
        ]);
        $installment = $this->inBranch((int)$loan->branch_id, fn () => $loan->installments()->firstOrFail());
        $admin = User::where('username','admin')->firstOrFail();

        $this->actingAs($admin)->post(route('loans.installments.excuse.store',[$loan,$installment]),[
            'reason'=>'Customer reported an emergency before the deadline.',
        ])->assertRedirect();

        $service = app(PaymentDeadlinePenaltyService::class);
        $this->assertSame(0,$service->assessLatePayment($loan->fresh(),CarbonImmutable::parse('2026-08-20 18:30:00')));
        $this->assertSame(0,$service->assessMissedSchedules($loan->fresh(),CarbonImmutable::parse('2026-08-21 00:05:00')));
        $this->assertSame(0,LoanPenaltyCharge::where('loan_id',$loan->id)->where('source','payment_deadline')->count());
        $this->assertDatabaseHas('loan_installment_excuses',[
            'loan_id'=>$loan->id,'loan_installment_id'=>$installment->id,'branch_id'=>$loan->branch_id,
        ]);
    }

    public function test_master_switch_and_product_deadline_controls_are_visible(): void
    {
        $admin = User::where('username','admin')->firstOrFail();

        $this->actingAs($admin)->get(route('admin.business-settings.edit'))
            ->assertOk()
            ->assertSee('Scheduled payment deadline penalties')
            ->assertSee('Enable payment-time deadline penalties')
            ->assertSee('Late same-day penalty')
            ->assertSee('Missed-day penalty');

        $this->actingAs($admin)->get(route('loan-products.create'))
            ->assertOk()
            ->assertSee('Scheduled payment deadline')
            ->assertSee('Apply a payment-time deadline to this product')
            ->assertSee('Daily deadline time');
    }

    /** @return array{0:Business,1:Branch,2:Loan} */
    private function deadlineLoan(string $tag, string $dueDate): array
    {
        $business = Business::firstOrFail();
        $branch = Branch::create(['business_id'=>$business->id,'name'=>'Deadline '.str($tag)->headline(),'status'=>'active']);
        $customer = Customer::create([
            'branch_id'=>$branch->id,'customer_number'=>'CUS-DL-'.strtoupper(substr(md5($tag),0,8)),
            'identity_mode'=>'without_nin','first_name'=>'Asha','last_name'=>str($tag)->headline()->toString(),
            'phone'=>'0766'.str_pad((string)$branch->id,6,'0',STR_PAD_LEFT),'status'=>'active',
        ]);
        $product = LoanProduct::create([
            'business_id'=>$business->id,'name'=>'Deadline Product '.str($tag)->headline(),
            'minimum_amount'=>'1000.00','maximum_amount'=>'500000.00','duration'=>1,'duration_unit'=>'months',
            'interest_rate'=>'0.000000','interest_calculation_method'=>'flat','repayment_frequency'=>'monthly','grace_period_days'=>0,
            'late_payment_penalty_type'=>'none','late_payment_penalty_value'=>'0.000000',
            'processing_fee_type'=>'fixed','processing_fee_value'=>'0.000000','processing_fee_payment_mode'=>'financed',
            'insurance_fee_type'=>'fixed','insurance_fee_value'=>'0.000000','other_charges'=>'0.00',
            'collateral_required'=>false,'guarantor_required'=>false,'payment_deadline_enabled'=>true,'payment_deadline_time'=>'17:00','status'=>'active',
        ]);
        $loan = Loan::create([
            'branch_id'=>$branch->id,'customer_id'=>$customer->id,'loan_product_id'=>$product->id,
            'sequence_number'=>1,'sequence_year'=>2026,'loan_number'=>'LN-DL-'.strtoupper(substr(md5($tag),0,8)),
            'principal'=>'10000.00','interest'=>'0.00','processing_fee'=>'0.00','processing_fee_payment_mode'=>'financed',
            'insurance_fee'=>'0.00','other_charges'=>'0.00','fees'=>'0.00','total_repayment'=>'10000.00',
            'principal_outstanding'=>'10000.00','interest_outstanding'=>'0.00','fees_outstanding'=>'0.00','penalties_outstanding'=>'0.00',
            'total_outstanding'=>'10000.00','total_paid'=>'0.00','late_payment_penalty_type'=>'none','late_payment_penalty_value'=>'0.000000',
            'payment_deadline_enabled'=>true,'payment_deadline_time'=>'17:00',
            'deadline_late_penalty_type'=>'fixed','deadline_late_penalty_value'=>'0.000000','deadline_late_penalty_settlement'=>'add_to_loan',
            'missed_schedule_penalty_type'=>'fixed','missed_schedule_penalty_value'=>'0.000000','missed_schedule_penalty_settlement'=>'add_to_loan',
            'purpose'=>'Deadline testing','schedule_start_date'=>$dueDate,'status'=>'active',
            'disbursed_at'=>$dueDate.' 09:00:00','activated_at'=>$dueDate.' 09:00:00','schedule_activated_at'=>$dueDate.' 09:00:00',
        ]);
        $loan->installments()->create([
            'installment_number'=>1,'principal'=>'10000.00','interest'=>'0.00','fees'=>'0.00','amount_due'=>'10000.00',
            'due_date'=>$dueDate,'amount_paid'=>'0.00','remaining'=>'10000.00','status'=>'upcoming',
            'principal_paid'=>'0.00','interest_paid'=>'0.00','fees_paid'=>'0.00','penalty_due'=>'0.00','penalty_paid'=>'0.00',
        ]);
        CustomerStatementEntry::create([
            'customer_id'=>$customer->id,'loan_id'=>$loan->id,'branch_id'=>$branch->id,'entry_date'=>$dueDate.' 09:00:00',
            'reference'=>'DSB-'.$tag,'type'=>'loan_disbursement','description'=>'Loan obligation '.$loan->loan_number,
            'debit'=>'10000.00','credit'=>'0.00','running_balance'=>'10000.00',
        ]);

        return [$business,$branch,$loan];
    }
}
