<?php

namespace Tests\Feature;

use App\Models\Branch;
use App\Models\Business;
use App\Models\Collateral;
use App\Models\CollateralCategory;
use App\Models\Customer;
use App\Models\Loan;
use App\Models\LoanProduct;
use App\Models\Role;
use App\Models\User;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class LoanApprovalWorkflowTest extends TestCase
{
    use RefreshDatabase;

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

    public function test_approval_required_mode_sends_to_review_and_reviewer_approval_becomes_ready_for_disbursement(): void
    {
        [$business,$branch,$customer,$product] = $this->fixture();
        $officer=$this->staff('loan-officer',$branch,'approval_officer');
        $inspector=$this->staff('loan-inspector',$branch,'approval_inspector');
        $loan=$this->createLoan($officer,$branch,$customer,$product);

        $this->submitForReview($officer,$loan);
        $this->assertSame('under_review',$loan->fresh()->status);

        $this->actingAs($inspector)->post(route('loans.approvals.decide',$loan),[
            'decision'=>'approve','comments'=>'Identity, capacity and security reviewed.',
        ])->assertRedirect();

        $loan->refresh();
        $this->assertSame('ready_for_disbursement',$loan->status);
        $this->assertSame($inspector->id,$loan->approved_by);
        $this->assertNotNull($loan->approved_at);
        $this->assertDatabaseHas('loan_approval_decisions',[
            'loan_id'=>$loan->id,'decision'=>'approve','previous_status'=>'under_review','new_status'=>'ready_for_disbursement','user_id'=>$inspector->id,'is_auto'=>false,
        ]);
        $this->assertDatabaseHas('loan_status_histories',['loan_id'=>$loan->id,'from_status'=>'under_review','to_status'=>'ready_for_disbursement']);
        $this->assertDatabaseHas('audit_logs',['action'=>'loan_approval.approve','auditable_id'=>$loan->id]);
    }

    public function test_reviewer_can_request_correction_documents_additional_collateral_and_reject_with_comments(): void
    {
        [$business,$branch,$customer,$product] = $this->fixture();
        $officer=$this->staff('loan-officer',$branch,'request_officer');
        $inspector=$this->staff('loan-inspector',$branch,'request_inspector');

        $correction=$this->createLoan($officer,$branch,$customer,$product,'Correction test');
        $this->submitForReview($officer,$correction);
        $this->actingAs($inspector)->post(route('loans.approvals.decide',$correction),['decision'=>'request_correction','comments'=>'Correct the stated business purpose.'])->assertRedirect();
        $this->assertSame('correction_requested',$correction->fresh()->status);
        $this->actingAs($officer)->get(route('loans.edit',$correction))->assertOk();

        $documents=$this->createLoan($officer,$branch,$customer,$product,'Documents test');
        $this->submitForReview($officer,$documents);
        $this->actingAs($inspector)->post(route('loans.approvals.decide',$documents),['decision'=>'request_documents','comments'=>'Upload current business records.'])->assertRedirect();
        $this->assertSame('waiting_for_documents',$documents->fresh()->status);

        $additional=$this->createLoan($officer,$branch,$customer,$product,'Collateral test');
        $this->submitForReview($officer,$additional);
        $this->actingAs($inspector)->post(route('loans.approvals.decide',$additional),['decision'=>'request_additional_collateral','comments'=>'More security is required.'])->assertRedirect();
        $this->assertSame('additional_collateral_requested',$additional->fresh()->status);
        $collateral=$this->collateral($business,$branch,$customer);
        $this->actingAs($officer)->post(route('loans.additional-collateral',$additional),['collateral_ids'=>[$collateral->id]])->assertRedirect();
        $this->assertTrue($additional->collaterals()->whereKey($collateral->id)->exists());
        $this->actingAs($officer)->post(route('loans.transition',$additional),['status'=>'under_review','notes'=>'Additional collateral attached.'])->assertRedirect();
        $this->assertSame('under_review',$additional->fresh()->status);

        $rejected=$this->createLoan($officer,$branch,$customer,$product,'Reject test');
        $this->submitForReview($officer,$rejected);
        $this->actingAs($inspector)->post(route('loans.approvals.decide',$rejected),['decision'=>'reject','comments'=>'Repayment capacity is insufficient.'])->assertRedirect();
        $this->assertSame('rejected',$rejected->fresh()->status);
        $this->assertDatabaseHas('loan_approval_decisions',['loan_id'=>$rejected->id,'decision'=>'reject','comments'=>'Repayment capacity is insufficient.']);
    }

    public function test_non_approval_decisions_require_comments(): void
    {
        [, $branch,$customer,$product] = $this->fixture();
        $officer=$this->staff('loan-officer',$branch,'comments_officer');
        $inspector=$this->staff('loan-inspector',$branch,'comments_inspector');
        $loan=$this->createLoan($officer,$branch,$customer,$product);
        $this->submitForReview($officer,$loan);

        $this->actingAs($inspector)->post(route('loans.approvals.decide',$loan),['decision'=>'reject'])
            ->assertSessionHasErrors('comments');
        $this->assertSame('under_review',$loan->fresh()->status);
    }

    public function test_maker_checker_blocks_creator_from_approving_their_own_application(): void
    {
        [$business,$branch,$customer,$product] = $this->fixture();
        $business->update(['maker_checker_enabled'=>true,'loan_approval_mode'=>'approval_required']);
        $manager=$this->staff('branch-manager',$branch,'maker_manager');
        $loan=$this->createLoan($manager,$branch,$customer,$product);
        $this->submitForReview($manager,$loan);

        $this->actingAs($manager)->post(route('loans.approvals.decide',$loan),['decision'=>'approve'])
            ->assertSessionHasErrors('decision');
        $this->assertSame('under_review',$loan->fresh()->status);
        $this->assertSame(0,$loan->approvalDecisions()->count());
    }

    public function test_loans_above_threshold_require_manager_authorized_approver(): void
    {
        [$business,$branch,$customer,$product] = $this->fixture();
        $business->update(['manager_approval_threshold'=>'400000.00']);
        $officer=$this->staff('loan-officer',$branch,'threshold_officer');
        $inspector=$this->staff('loan-inspector',$branch,'threshold_inspector');
        $manager=$this->staff('branch-manager',$branch,'threshold_manager');
        $loan=$this->createLoan($officer,$branch,$customer,$product);
        $this->submitForReview($officer,$loan);

        $this->actingAs($inspector)->post(route('loans.approvals.decide',$loan),['decision'=>'approve'])
            ->assertSessionHasErrors('decision');
        $this->assertSame('under_review',$loan->fresh()->status);

        $this->actingAs($manager)->post(route('loans.approvals.decide',$loan),['decision'=>'approve','comments'=>'Manager threshold review completed.'])->assertRedirect();
        $this->assertSame('ready_for_disbursement',$loan->fresh()->status);
        $this->assertSame($manager->id,$loan->fresh()->approved_by);
    }

    public function test_authorized_auto_approval_mode_only_auto_approves_staff_with_permission(): void
    {
        [$business,$branch,$customer,$product] = $this->fixture();
        $business->update(['loan_approval_mode'=>'auto_authorized','maker_checker_enabled'=>false,'manager_approval_threshold'=>null]);

        $officer=$this->staff('loan-officer',$branch,'auto_officer');
        $officerLoan=$this->createLoan($officer,$branch,$customer,$product,'Officer auto test');
        $this->submitForReview($officer,$officerLoan);
        $this->assertSame('under_review',$officerLoan->fresh()->status);
        $this->assertSame(0,$officerLoan->approvalDecisions()->count());

        $manager=$this->staff('branch-manager',$branch,'auto_manager');
        $managerLoan=$this->createLoan($manager,$branch,$customer,$product,'Manager auto test');
        $this->actingAs($manager)->post(route('loans.transition',$managerLoan),['status'=>'application_submitted'])->assertRedirect();
        $this->actingAs($manager)->post(route('loans.transition',$managerLoan),['status'=>'under_review','notes'=>'Authorized auto approval submission.'])->assertRedirect();

        $managerLoan->refresh();
        $this->assertSame('ready_for_disbursement',$managerLoan->status);
        $this->assertSame($manager->id,$managerLoan->approved_by);
        $this->assertDatabaseHas('loan_approval_decisions',['loan_id'=>$managerLoan->id,'decision'=>'auto_approve','new_status'=>'ready_for_disbursement','user_id'=>$manager->id,'is_auto'=>true]);
        $this->assertDatabaseHas('audit_logs',['action'=>'loan_approval.auto_approved','auditable_id'=>$managerLoan->id]);
    }

    public function test_approval_queue_is_permission_and_branch_scoped_and_settings_render(): void
    {
        [$business,$branchA,$customer,$product] = $this->fixture();
        $branchB=Branch::create(['business_id'=>$business->id,'name'=>'Mwanza Approval','status'=>'active']);
        $officer=$this->staff('loan-officer',$branchA,'queue_officer');
        $inspectorA=$this->staff('loan-inspector',$branchA,'queue_inspector_a');
        $inspectorB=$this->staff('loan-inspector',$branchB,'queue_inspector_b');
        $loan=$this->createLoan($officer,$branchA,$customer,$product);
        $this->submitForReview($officer,$loan);

        $this->actingAs($officer)->get(route('loans.approvals.index'))->assertForbidden();
        $this->actingAs($inspectorA)->get(route('loans.approvals.index'))->assertOk()->assertSee($loan->loan_number)->assertSee('data-ui="table-toolbar"',false);
        $this->actingAs($inspectorB)->get(route('loans.approvals.index'))->assertOk()->assertDontSee($loan->loan_number);

        $admin=User::where('email','admin@microfinance.local')->firstOrFail();
        $this->actingAs($admin)->get(route('admin.business-settings.edit'))->assertOk()
            ->assertSee('Loan approval controls')->assertSee('Enable maker-checker')->assertSee('Manager approval threshold');
    }

    private function fixture(): array
    {
        $business=Business::firstOrFail();
        $branch=Branch::firstOrCreate(['business_id'=>$business->id,'name'=>'Dodoma Approval'],['status'=>'active']);
        $customer=Customer::firstOrCreate(['customer_number'=>'CUS-APPROVAL-001'],[
            'branch_id'=>$branch->id,'identity_mode'=>'without_nin','first_name'=>'Neema','last_name'=>'John','phone'=>'0755333000','status'=>'active',
        ]);
        $product=LoanProduct::firstOrCreate(['business_id'=>$business->id,'name'=>'Approval Product'],[
            'minimum_amount'=>'100000.00','maximum_amount'=>'1000000.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'=>'0.000000',
            'insurance_fee_type'=>'fixed','insurance_fee_value'=>'0.000000','other_charges'=>'0.00','collateral_required'=>false,'guarantor_required'=>false,'status'=>'active',
        ]);
        return [$business,$branch,$customer,$product];
    }

    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 createLoan(User $creator, Branch $branch, Customer $customer, LoanProduct $product, string $purpose='Working capital'): Loan
    {
        $this->actingAs($creator)->post(route('loans.store'),[
            'branch_id'=>$branch->id,'customer_id'=>$customer->id,'loan_product_id'=>$product->id,
            'principal'=>'500000.00','purpose'=>$purpose,'schedule_start_date'=>now()->format('Y-m-d'),
        ])->assertRedirect();
        return Loan::where('customer_id',$customer->id)->latest('id')->firstOrFail();
    }

    private function submitForReview(User $user, Loan $loan): void
    {
        $this->actingAs($user)->post(route('loans.transition',$loan),['status'=>'application_submitted'])->assertRedirect();
        $this->actingAs($user)->post(route('loans.transition',$loan),['status'=>'under_review','notes'=>'Submitted for approval.'])->assertRedirect();
    }

    private function collateral(Business $business, Branch $branch, Customer $customer): Collateral
    {
        $category=CollateralCategory::where('business_id',$business->id)->firstOrFail();
        return Collateral::create([
            'customer_id'=>$customer->id,'branch_id'=>$branch->id,'category_id'=>$category->id,'sequence_number'=>99001,
            'code'=>'COL-APPROVAL-000001','item'=>'Motorcycle','accepted_value'=>'700000.00','received_at'=>now(),'status'=>'accepted',
        ]);
    }
}
