<?php

namespace App\Http\Controllers;

use App\Models\Branch;
use App\Models\Collateral;
use App\Models\CollateralCategory;
use App\Models\CollateralFile;
use App\Models\CollateralMovement;
use App\Models\Customer;
use App\Models\User;
use App\Services\AuditService;
use App\Services\BranchAccessService;
use App\Services\Code39BarcodeService;
use App\Services\CreditScore\CreditScoreRefreshCoordinator;
use App\Services\SecureFileService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;

class CollateralController extends Controller
{
    public function __construct(
        private BranchAccessService $branchAccess,
        private CreditScoreRefreshCoordinator $creditScores,
    ) {
    }

    public function index(Request $request): View
    {
        $query = Collateral::with(['customer', 'branch', 'category'])->latest('id');
        $query->whereIn('branch_id', $this->allowedBranchIds($request));

        if ($request->filled('search')) {
            $search = trim((string) $request->string('search'));
            $query->where(function ($builder) use ($search): void {
                $builder->where('code', 'like', "%{$search}%")
                    ->orWhere('item', 'like', "%{$search}%")
                    ->orWhere('serial_number', 'like', "%{$search}%")
                    ->orWhereHas('customer', function ($customer) use ($search): void {
                        $customer->where('customer_number', 'like', "%{$search}%")
                            ->orWhere('first_name', 'like', "%{$search}%")
                            ->orWhere('middle_name', 'like', "%{$search}%")
                            ->orWhere('last_name', 'like', "%{$search}%")
                            ->orWhere('phone', 'like', "%{$search}%");
                    });
            });
        }

        if ($request->filled('status')) {
            $query->where('status', $request->string('status'));
        }

        if ($request->filled('category')) {
            $query->where('category_id', $request->integer('category'));
        }

        return view('collateral.index', [
            'collaterals' => $query->paginate(20)->withQueryString(),
            'categories' => $this->availableCategories($request),
            'statuses' => Collateral::STATUSES,
        ]);
    }

    public function create(Request $request): View
    {
        $branchIds = $this->allowedBranchIds($request);

        return view('collateral.form', [
            'collateral' => new Collateral(['received_at' => now(), 'status' => 'pending_inspection']),
            'branches' => Branch::whereIn('id', $branchIds)->where('status', 'active')->orderBy('name')->get(),
            'customers' => Customer::whereIn('branch_id', $branchIds)->where('status', 'active')->orderBy('first_name')->orderBy('last_name')->get(),
            'categories' => $this->availableCategories($request, true),
            'receivers' => $this->assignedStaff($branchIds),
            'statuses' => Collateral::STATUSES,
        ]);
    }

    public function store(Request $request, SecureFileService $files): RedirectResponse
    {
        $data = $this->validated($request);
        $this->assertBranchAccess($request, (int) $data['branch_id']);
        $this->assertCustomerAndCategory($data);
        $this->assertReceiverAssignment($data['received_by'] ?? null, (int) $data['branch_id']);

        $collateral = DB::transaction(function () use ($request, $data, $files): Collateral {
            $branch = Branch::query()->lockForUpdate()->findOrFail($data['branch_id']);
            $sequence = ((int) Collateral::query()->where('branch_id', $branch->id)->max('sequence_number')) + 1;
            $code = 'COL-'.$this->branchTag($branch).'-'.str_pad((string) $sequence, 6, '0', STR_PAD_LEFT);

            $record = Collateral::create(array_merge($data, [
                'sequence_number' => $sequence,
                'code' => $code,
                'created_by' => $request->user()->id,
                'updated_by' => $request->user()->id,
            ]));

            CollateralMovement::create([
                'collateral_id' => $record->id,
                'action' => 'received',
                'to_status' => $record->status,
                'to_store_room' => $record->store_room,
                'to_shelf' => $record->shelf,
                'notes' => 'Collateral received and registered.',
                'moved_by' => $request->user()->id,
                'moved_at' => now(),
            ]);

            $this->storeInitialFiles($request, $record, $files);

            return $record;
        });

        AuditService::record('collateral.created', $collateral, [], $collateral->fresh()->toArray(), $collateral->branch_id);

        return redirect()->route('collateral.show', $collateral)->with('success', 'Collateral registered successfully.');
    }

    public function show(Request $request, Collateral $collateral, Code39BarcodeService $barcode): View
    {
        $this->assertBranchAccess($request, $collateral->branch_id);

        return view('collateral.show', [
            'collateral' => $collateral->load(['customer', 'branch', 'category', 'receiver', 'files.uploader', 'movements.mover']),
            'barcodeSvg' => $barcode->svg($collateral->code),
            'statuses' => Collateral::STATUSES,
        ]);
    }

    public function edit(Request $request, Collateral $collateral): View
    {
        $this->assertBranchAccess($request, $collateral->branch_id);

        return view('collateral.form', [
            'collateral' => $collateral->load(['customer', 'branch']),
            'branches' => Branch::whereKey($collateral->branch_id)->get(),
            'customers' => Customer::where('branch_id', $collateral->branch_id)->orderBy('first_name')->orderBy('last_name')->get(),
            'categories' => $this->availableCategories($request, true),
            'receivers' => $this->assignedStaff(collect([$collateral->branch_id])),
            'statuses' => Collateral::STATUSES,
        ]);
    }

    public function update(Request $request, Collateral $collateral): RedirectResponse
    {
        $this->assertBranchAccess($request, $collateral->branch_id);
        $data = $this->validated($request, $collateral);
        $data['branch_id'] = $collateral->branch_id;
        $data['customer_id'] = $collateral->customer_id;
        $this->assertCustomerAndCategory($data);
        $this->assertReceiverAssignment($data['received_by'] ?? null, $collateral->branch_id);

        $before = $collateral->toArray();
        $beforeAcceptedValue = (string) ($collateral->accepted_value ?? '');
        unset($data['status'], $data['store_room'], $data['shelf']);
        $collateral->update(array_merge($data, ['updated_by' => $request->user()->id]));
        $fresh = $collateral->fresh();

        AuditService::record('collateral.updated', $collateral, $before, $fresh->toArray(), $collateral->branch_id);

        if ($beforeAcceptedValue !== (string) ($fresh->accepted_value ?? '') && $this->supportsActiveCredit($fresh)) {
            $this->creditScores->afterCommitIds($fresh->customer_id, $fresh->branch_id, 'collateral_value_updated');
        }

        return redirect()->route('collateral.show', $collateral)->with('success', 'Collateral details updated.');
    }

    public function move(Request $request, Collateral $collateral): RedirectResponse
    {
        $this->assertBranchAccess($request, $collateral->branch_id);
        $data = $request->validate([
            'status' => ['required', Rule::in(Collateral::STATUSES)],
            'store_room' => ['nullable', 'string', 'max:150'],
            'shelf' => ['nullable', 'string', 'max:150'],
            'notes' => ['nullable', 'string', 'max:2000'],
        ]);

        $before = $collateral->only(['status', 'store_room', 'shelf']);
        abort_if(
            $before['status'] === $data['status'] &&
            (string) $before['store_room'] === (string) ($data['store_room'] ?? null) &&
            (string) $before['shelf'] === (string) ($data['shelf'] ?? null),
            422,
            'No custody or status change was provided.'
        );

        DB::transaction(function () use ($request, $collateral, $data, $before): void {
            $collateral->update([
                'status' => $data['status'],
                'store_room' => $data['store_room'] ?? null,
                'shelf' => $data['shelf'] ?? null,
                'updated_by' => $request->user()->id,
            ]);

            $statusChanged = $before['status'] !== $data['status'];
            $locationChanged = (string) $before['store_room'] !== (string) ($data['store_room'] ?? null)
                || (string) $before['shelf'] !== (string) ($data['shelf'] ?? null);

            CollateralMovement::create([
                'collateral_id' => $collateral->id,
                'action' => $statusChanged && $locationChanged ? 'status_and_location_changed' : ($statusChanged ? 'status_changed' : 'location_changed'),
                'from_status' => $before['status'],
                'to_status' => $data['status'],
                'from_store_room' => $before['store_room'],
                'from_shelf' => $before['shelf'],
                'to_store_room' => $data['store_room'] ?? null,
                'to_shelf' => $data['shelf'] ?? null,
                'notes' => $data['notes'] ?? null,
                'moved_by' => $request->user()->id,
                'moved_at' => now(),
            ]);
        });

        $fresh = $collateral->fresh();
        AuditService::record('collateral.custody_changed', $collateral, $before, $fresh->only(['status', 'store_room', 'shelf']), $collateral->branch_id);

        if ($before['status'] !== $fresh->status && $this->supportsActiveCredit($fresh)) {
            $this->creditScores->afterCommitIds($fresh->customer_id, $fresh->branch_id, 'collateral_status_updated');
        }

        return back()->with('success', 'Collateral custody/status updated.');
    }

    public function storeFile(Request $request, Collateral $collateral, SecureFileService $files): RedirectResponse
    {
        $this->assertBranchAccess($request, $collateral->branch_id);
        $kind = (string) $request->input('kind');
        $mimeRule = $kind === 'photo'
            ? 'mimes:jpg,jpeg,png,webp'
            : 'mimes:jpg,jpeg,png,webp,pdf,doc,docx';

        $data = $request->validate([
            'kind' => ['required', Rule::in(['photo', 'document'])],
            'document_type' => ['nullable', 'required_if:kind,document', 'string', 'max:150'],
            'attachments' => ['nullable', 'array', 'max:20'],
            'attachments.*' => ['file', $mimeRule, 'max:8192'],
            // Keep the old single-file field working for existing clients/forms.
            'attachment' => ['nullable', 'file', $mimeRule, 'max:8192'],
            'notes' => ['nullable', 'string', 'max:1000'],
        ]);

        $uploads = collect($request->file('attachments', []));
        if ($request->hasFile('attachment')) {
            $uploads->push($request->file('attachment'));
        }

        if ($uploads->isEmpty()) {
            throw ValidationException::withMessages([
                'attachments' => 'Select at least one file to upload.',
            ]);
        }

        $directory = 'collateral/'.$collateral->id.'/'.($data['kind'] === 'photo' ? 'photos' : 'documents');

        foreach ($uploads as $file) {
            $path = $files->store($file, $directory);
            $record = CollateralFile::create([
                'collateral_id' => $collateral->id,
                'kind' => $data['kind'],
                'document_type' => $data['kind'] === 'document' ? ($data['document_type'] ?? null) : null,
                'original_name' => $file->getClientOriginalName(),
                'file_path' => $path,
                'mime_type' => $file->getMimeType(),
                'file_size' => $file->getSize(),
                'notes' => $data['notes'] ?? null,
                'uploaded_by' => $request->user()->id,
            ]);

            AuditService::record('collateral.file_uploaded', $record, [], $record->toArray(), $collateral->branch_id);
        }

        $count = $uploads->count();
        $label = $data['kind'] === 'photo' ? Str::plural('photo', $count) : Str::plural('document', $count);

        return back()->with('success', $count.' collateral '.$label.' uploaded successfully.');
    }

    public function downloadFile(Request $request, Collateral $collateral, CollateralFile $file, SecureFileService $files): StreamedResponse
    {
        $this->assertBranchAccess($request, $collateral->branch_id);
        abort_unless($file->collateral_id === $collateral->id, 404);

        return $files->download($file->file_path, $file->original_name);
    }

    public function receipt(Request $request, Collateral $collateral, Code39BarcodeService $barcode): View
    {
        $this->assertBranchAccess($request, $collateral->branch_id);

        return view('collateral.receipt', [
            'collateral' => $collateral->load(['customer', 'branch', 'category', 'receiver']),
            'barcodeSvg' => $barcode->svg($collateral->code, 56),
            'title' => 'Collateral Receipt',
            'acknowledgement' => null,
        ]);
    }

    public function label(Request $request, Collateral $collateral, Code39BarcodeService $barcode): View
    {
        $this->assertBranchAccess($request, $collateral->branch_id);

        return view('collateral.label', [
            'collateral' => $collateral->load(['customer', 'branch']),
            'barcodeSvg' => $barcode->svg($collateral->code, 76),
        ]);
    }

    public function releaseAcknowledgement(Request $request, Collateral $collateral, Code39BarcodeService $barcode): View
    {
        $this->assertBranchAccess($request, $collateral->branch_id);
        $movement = $collateral->movements()
            ->whereIn('to_status', ['released', 'returned'])
            ->with('mover')
            ->latest('moved_at')
            ->first();
        abort_unless($movement, 404, 'No release/return movement has been recorded for this collateral.');

        return view('collateral.receipt', [
            'collateral' => $collateral->load(['customer', 'branch', 'category', 'receiver']),
            'barcodeSvg' => $barcode->svg($collateral->code, 56),
            'title' => 'Collateral Release Acknowledgement',
            'acknowledgement' => $movement,
        ]);
    }

    private function validated(Request $request, ?Collateral $collateral = null): array
    {
        return $request->validate([
            'customer_id' => [$collateral ? 'nullable' : 'required', 'integer', 'exists:customers,id'],
            'branch_id' => [$collateral ? 'nullable' : 'required', 'integer', 'exists:branches,id'],
            'category_id' => ['required', 'integer', 'exists:collateral_categories,id'],
            'item' => ['required', 'string', 'max:200'],
            'brand' => ['nullable', 'string', 'max:150'],
            'model' => ['nullable', 'string', 'max:150'],
            'serial_number' => ['nullable', 'string', 'max:150'],
            'colour' => ['nullable', 'string', 'max:100'],
            'description' => ['nullable', 'string', 'max:4000'],
            'estimated_value' => ['nullable', 'decimal:0,2', 'min:0'],
            'accepted_value' => ['nullable', 'decimal:0,2', 'min:0'],
            'condition' => ['nullable', 'string', 'max:150'],
            'received_at' => ['required', 'date'],
            'store_room' => ['nullable', 'string', 'max:150'],
            'shelf' => ['nullable', 'string', 'max:150'],
            'received_by' => ['nullable', 'integer', 'exists:users,id'],
            'status' => ['required', Rule::in(Collateral::STATUSES)],
            'photos' => ['nullable', 'array', 'max:20'],
            'photos.*' => ['image', 'mimes:jpg,jpeg,png,webp', 'max:8192'],
            'documents' => ['nullable', 'array', 'max:20'],
            'documents.*' => ['file', 'mimes:jpg,jpeg,png,webp,pdf,doc,docx', 'max:8192'],
        ]);
    }

    private function assertCustomerAndCategory(array $data): void
    {
        $branch = Branch::findOrFail($data['branch_id']);
        $customer = Customer::findOrFail($data['customer_id']);
        $category = CollateralCategory::findOrFail($data['category_id']);

        abort_unless($customer->branch_id === $branch->id, 422, 'Customer must belong to the selected branch.');
        abort_unless($category->business_id === $branch->business_id, 422, 'Collateral category must belong to the branch business.');
    }

    private function assertReceiverAssignment(mixed $userId, int $branchId): void
    {
        if (! $userId) return;
        $receiver = User::findOrFail((int) $userId);
        if ($receiver->status !== 'active' || ! $this->branchAccess->isAssignedToBranch($receiver, $branchId)) {
            throw ValidationException::withMessages(['received_by' => 'The selected receiver must be active staff assigned to the collateral branch.']);
        }
    }

    private function assignedStaff($branchIds)
    {
        $ids = collect($branchIds)->map(fn ($id) => (int) $id)->unique()->values()->all();

        return User::query()
            ->where('status', 'active')
            ->where(function ($query) use ($ids): void {
                $query->whereIn('primary_branch_id', $ids)
                    ->orWhereHas('branches', fn ($branches) => $branches->whereIn('branches.id', $ids));
            })
            ->orderBy('name')
            ->get();
    }

    private function storeInitialFiles(Request $request, Collateral $collateral, SecureFileService $files): void
    {
        foreach ($request->file('photos', []) as $file) {
            $path = $files->store($file, 'collateral/'.$collateral->id.'/photos');
            CollateralFile::create([
                'collateral_id' => $collateral->id,
                'kind' => 'photo',
                'original_name' => $file->getClientOriginalName(),
                'file_path' => $path,
                'mime_type' => $file->getMimeType(),
                'file_size' => $file->getSize(),
                'uploaded_by' => $request->user()->id,
            ]);
        }

        foreach ($request->file('documents', []) as $file) {
            $path = $files->store($file, 'collateral/'.$collateral->id.'/documents');
            CollateralFile::create([
                'collateral_id' => $collateral->id,
                'kind' => 'document',
                'document_type' => 'Supporting document',
                'original_name' => $file->getClientOriginalName(),
                'file_path' => $path,
                'mime_type' => $file->getMimeType(),
                'file_size' => $file->getSize(),
                'uploaded_by' => $request->user()->id,
            ]);
        }
    }

    private function availableCategories(Request $request, bool $activeOnly = false)
    {
        $businessIds = Branch::whereIn('id', $this->allowedBranchIds($request))->pluck('business_id')->unique();
        $query = CollateralCategory::whereIn('business_id', $businessIds)->orderBy('name');
        if ($activeOnly) {
            $query->where('status', 'active');
        }

        return $query->get();
    }

    private function allowedBranchIds(Request $request)
    {
        return collect($this->branchAccess->accessibleBranchIds($request->user()));
    }

    private function assertBranchAccess(Request $request, int $branchId): void
    {
        $this->branchAccess->assertBranchAccessible($request->user(), $branchId);
    }

    private function supportsActiveCredit(Collateral $collateral): bool
    {
        return $collateral->loans()->where('loans.status', 'active')->exists();
    }

    private function branchTag(Branch $branch): string
    {
        $code = Str::upper((string) $branch->code);
        $code = preg_replace('/^BR-/', '', $code) ?: '';
        $code = preg_replace('/[^A-Z0-9]/', '', $code) ?: '';

        return Str::limit($code ?: 'B'.$branch->id, 8, '');
    }
}
