<?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\FinancialAccount;
use App\Models\Loan;
use App\Models\LoanPayment;
use App\Models\LoanProduct;
use App\Models\Permission;
use App\Models\RemotePaymentSubmission;
use App\Models\Role;
use App\Models\User;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class GlobalSearchTest extends TestCase
{
    use RefreshDatabase;

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

    public function test_global_search_finds_every_roadmap_identifier_and_links_to_existing_records(): void
    {
        [$branch,$manager,$customer,$loan,$collateral,$payment,$remote] = $this->fixture('roadmap');

        $checks = [
            ['Amina Roadmap', route('customers.show',$customer), $customer->customer_number],
            [$customer->phone, route('customers.show',$customer), $customer->phone],
            [$customer->nin, route('customers.show',$customer), $customer->nin],
            [$customer->customer_number, route('customers.show',$customer), $customer->customer_number],
            [$loan->loan_number, route('loans.show',$loan), $loan->loan_number],
            [$collateral->code, route('collateral.show',$collateral), $collateral->code],
            [$payment->receipt_number, route('loan-payments.receipt',$payment), $payment->receipt_number],
            [$payment->reference, route('loan-payments.receipt',$payment), $payment->reference],
            [$remote->transaction_reference, route('remote-payments.show',$remote), $remote->transaction_reference],
        ];

        foreach ($checks as [$query,$url,$visible]) {
            $this->actingAs($manager)->get(route('global-search.index',['q'=>$query]))
                ->assertOk()->assertSee($visible)->assertSee($url,false);
        }

        $this->actingAs($manager)->get(route('global-search.index',['q'=>'Amina Roadmap']))
            ->assertOk()->assertSee('Customers')->assertSee($branch->name);
    }

    public function test_global_search_enforces_branch_scope_and_super_administrator_can_search_every_branch(): void
    {
        [$branchA,$managerA] = $this->fixture('scope-a');
        [$branchB,,$customerB,$loanB,$collateralB,$paymentB,$remoteB] = $this->fixture('scope-b');

        foreach ([$customerB->customer_number,$loanB->loan_number,$collateralB->code,$paymentB->receipt_number,$remoteB->transaction_reference] as $query) {
            $this->actingAs($managerA)->get(route('global-search.index',['q'=>$query]))
                ->assertOk()->assertSee('No accessible records match')->assertDontSee($branchB->name);
        }

        $admin = User::where('email','admin@microfinance.local')->firstOrFail();
        $this->actingAs($admin)->get(route('global-search.index',['q'=>$loanB->loan_number]))
            ->assertOk()->assertSee($loanB->loan_number)->assertSee($branchB->name);
        $this->actingAs($admin)->get(route('global-search.index',['q'=>$remoteB->transaction_reference]))
            ->assertOk()->assertSee($remoteB->transaction_reference);

        $this->assertTrue($managerA->canAccessBranch($branchA->id));
        $this->assertFalse($managerA->canAccessBranch($branchB->id));
    }

    public function test_global_search_respects_module_permissions_and_cannot_be_used_as_a_side_channel(): void
    {
        [$branch,,$customer,$loan,$collateral,$payment] = $this->fixture('permissions');
        $this->inBranch((int)$branch->id, function () use ($customer,$loan,$collateral,$payment): void {
            $customer->update(['first_name'=>'Common23','last_name'=>'Customer']);
            $loan->forceFill(['loan_number'=>'LN-COMMON23'])->save();
            $collateral->forceFill(['code'=>'COL-COMMON23'])->save();
            $payment->update(['reference'=>'PAY-COMMON23']);
        });

        $role = Role::create(['name'=>'customer-search-only','display_name'=>'Customer Search Only']);
        $role->permissions()->attach(Permission::whereIn('name',['search.use','customers.view'])->pluck('id'));
        $user = User::factory()->create(['name'=>'Customer Search User','username'=>'customer_search_only','status'=>'active','primary_branch_id'=>$branch->id]);
        $user->roles()->attach($role->id);
        $user->branches()->attach($branch->id,['is_primary'=>true]);

        $response = $this->actingAs($user)->get(route('global-search.index',['q'=>'COMMON23']))->assertOk();
        $response->assertSee('Customers')->assertSee('Common23 Customer');
        $response->assertDontSee('Loans')->assertDontSee('Collateral')->assertDontSee('Payment Receipts')->assertDontSee('Remote Payment References');
        $response->assertDontSee($loan->loan_number)->assertDontSee($collateral->code)->assertDontSee($payment->reference);

        $this->actingAs($user)->get(route('loans.show',$loan))->assertForbidden();
        $this->actingAs($user)->get(route('collateral.show',$collateral))->assertForbidden();
    }

    public function test_remote_payment_reference_requires_verification_permission(): void
    {
        [$branch,$manager,,,$collateral,$payment,$remote] = $this->fixture('remote-permission');
        $officer = $this->staff('loan-officer',$branch,'step23_remote_officer');

        $this->actingAs($officer)->get(route('global-search.index',['q'=>$remote->transaction_reference]))
            ->assertOk()->assertSee('No accessible records match')->assertDontSee('Remote Payment References');
        $this->actingAs($manager)->get(route('global-search.index',['q'=>$remote->transaction_reference]))
            ->assertOk()->assertSee('Remote Payment References')->assertSee(route('remote-payments.show',$remote),false);

        $this->assertNotSame($payment->reference,$remote->transaction_reference);
        $this->assertNotEmpty($collateral->code);
    }

    public function test_search_route_short_queries_topbar_and_keyboard_shortcut_are_registered(): void
    {
        [$branch,$manager] = $this->fixture('ui');

        $this->get(route('global-search.index'))->assertRedirect(route('login'));
        $this->actingAs($manager)->get(route('global-search.index',['q'=>'A']))
            ->assertOk()->assertSee('Enter at least 2 characters to search.');

        $this->actingAs($manager)->get(route('dashboard'))->assertOk()
            ->assertSee(route('global-search.index'),false)
            ->assertSee('data-global-search-input',false)
            ->assertSee('Search records...');

        $javascript = file_get_contents(public_path('assets/microfinance/app.js'));
        $this->assertStringContainsString('event.metaKey || event.ctrlKey',$javascript);
        $this->assertStringContainsString("event.key.toLowerCase() !== 'k'",$javascript);
        $this->assertStringContainsString('globalSearchInput.focus()',$javascript);
        $this->assertTrue($manager->canAccessBranch($branch->id));
    }

    private function fixture(string $tag): array
    {
        $business = Business::firstOrFail();
        $branch = Branch::create(['business_id'=>$business->id,'name'=>'Step23 '.str($tag)->headline(),'status'=>'active']);
        $manager = $this->staff('branch-manager',$branch,'step23_manager_'.str_replace('-','_',$tag));
        $customerCount = $this->systemWide(fn () => Customer::count());
        $customer = Customer::create([
            'branch_id'=>$branch->id,'customer_number'=>'CUS-S23-'.strtoupper(substr(md5($tag),0,8)),'identity_mode'=>'with_nin',
            'first_name'=>'Amina','last_name'=>str($tag)->headline()->toString(),'nin'=>'NIN-S23-'.strtoupper(substr(md5('nin-'.$tag),0,8)),
            'phone'=>'07'.str_pad((string)(10000000 + $customerCount),8,'0',STR_PAD_LEFT),'email'=>'s23-'.$tag.'@example.test','status'=>'active','created_by'=>$manager->id,
        ]);
        $product = LoanProduct::create([
            'business_id'=>$business->id,'name'=>'Step23 Product '.str($tag)->headline(),'minimum_amount'=>'100.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'=>'none','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',
        ]);
        $sequence = $this->inBranch((int)$branch->id, fn () => Loan::where('branch_id',$branch->id)->count())+1;
        $loan = Loan::create([
            'branch_id'=>$branch->id,'customer_id'=>$customer->id,'loan_product_id'=>$product->id,'sequence_number'=>$sequence,'sequence_year'=>2026,
            'loan_number'=>'LN-S23-'.strtoupper(str_replace('-','',$tag)).'-'.$branch->id,'principal'=>'100000.00','interest'=>'10000.00','processing_fee'=>'0.00','insurance_fee'=>'0.00',
            'other_charges'=>'0.00','fees'=>'0.00','total_repayment'=>'110000.00','principal_outstanding'=>'100000.00','interest_outstanding'=>'10000.00',
            'fees_outstanding'=>'0.00','penalties_outstanding'=>'0.00','total_outstanding'=>'110000.00','total_paid'=>'0.00','overdue_principal'=>'0.00','overdue_interest'=>'0.00',
            'overdue_fees'=>'0.00','overdue_penalties'=>'0.00','total_overdue'=>'0.00','days_overdue'=>0,'late_payment_penalty_type'=>'none','late_payment_penalty_value'=>'0.000000',
            'purpose'=>'Global search fixture','schedule_start_date'=>'2026-08-01','status'=>'active','disbursed_at'=>'2026-08-01 08:00:00','activated_at'=>'2026-08-01 08:00:00',
            'schedule_activated_at'=>'2026-08-01 08:00:00','created_by'=>$manager->id,
        ]);
        $category = CollateralCategory::where('business_id',$business->id)->firstOrFail();
        $collateralCount = $this->systemWide(fn () => Collateral::count());
        $collateral = Collateral::create([
            'customer_id'=>$customer->id,'branch_id'=>$branch->id,'category_id'=>$category->id,'sequence_number'=>9000 + $collateralCount+1,
            'code'=>'COL-S23-'.strtoupper(str_replace('-','',$tag)).'-'.$branch->id,'item'=>'Television '.$tag,'estimated_value'=>'200000.00','accepted_value'=>'150000.00',
            'received_at'=>'2026-08-01','status'=>'in_custody','received_by'=>$manager->id,'created_by'=>$manager->id,
        ]);
        $account = FinancialAccount::create([
            'branch_id'=>$branch->id,'name'=>'Step23 Cash '.$tag,'account_type'=>'cash','opening_balance'=>'500000.00','current_balance'=>'500000.00','status'=>'active',
        ]);
        $payment = LoanPayment::create([
            'receipt_number'=>'RCP-S23-'.strtoupper(str_replace('-','',$tag)).'-'.$branch->id,'loan_id'=>$loan->id,'customer_id'=>$customer->id,'branch_id'=>$branch->id,
            'financial_account_id'=>$account->id,'amount'=>'10000.00','principal_amount'=>'9000.00','interest_amount'=>'1000.00','fees_amount'=>'0.00','penalties_amount'=>'0.00',
            'payment_date'=>'2026-08-10 10:00:00','method'=>'mobile_money','reference'=>'PAYREF-S23-'.strtoupper(str_replace('-','',$tag)),'received_by'=>$manager->id,
        ]);
        $remote = RemotePaymentSubmission::create([
            'submission_number'=>'RPS-S23-'.strtoupper(str_replace('-','',$tag)).'-'.$branch->id,'loan_id'=>$loan->id,'customer_id'=>$customer->id,'branch_id'=>$branch->id,
            'amount'=>'5000.00','payment_channel'=>'mpesa','transaction_reference'=>'REMOTEREF-S23-'.strtoupper(str_replace('-','',$tag)),
            'proof_path'=>'private/step23-'.$tag.'.jpg','proof_original_name'=>'proof.jpg','proof_mime_type'=>'image/jpeg','proof_file_size'=>1200,
            'status'=>'pending_verification','submitted_by_type'=>'staff','submitted_by_user_id'=>$manager->id,'submitted_at'=>'2026-08-11 09:00:00',
        ]);

        return [$branch,$manager,$customer,$loan,$collateral,$payment,$remote];
    }

    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;
    }
}
