<?php

namespace App\Http\Controllers;

use App\Domain\Financial\Models\Expense;
use App\Domain\Shared\Context\BranchContext;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;

/**
 * Receipts live on the private disk, so they can only be read back through
 * here — never by guessing a URL under /storage.
 */
class ExpenseAttachmentController extends Controller
{
    public function show(Expense $expense): StreamedResponse
    {
        return $this->stream($expense, 'inline');
    }

    public function download(Expense $expense): StreamedResponse
    {
        return $this->stream($expense, 'attachment');
    }

    /**
     * The disposition header is built by Symfony, never by hand: receipt names
     * here are Arabic more often than not, and `filename="فاتورة.jpg"` is not a
     * legal header value — the browser got a mangled name, or nothing at all,
     * and the inline preview on the expense page rendered a broken image.
     * makeDisposition() writes the RFC 6266 `filename*` form with an ASCII
     * fallback, which is what both cases need.
     */
    private function stream(Expense $expense, string $disposition): StreamedResponse
    {
        Gate::authorize('expenses.create');
        $this->ensureVisible($expense);

        abort_unless($expense->hasAttachment(), 404);

        $disk = Storage::disk($expense->attachmentDiskName());

        abort_unless($disk->exists($expense->attachment_path), 404);

        $filename = $expense->attachment_name ?: basename($expense->attachment_path);

        $headers = ['Content-Type' => $expense->attachment_mime ?: 'application/octet-stream'];

        return $disposition === 'attachment'
            ? $disk->download($expense->attachment_path, $filename, $headers)
            : $disk->response($expense->attachment_path, $filename, $headers, 'inline');
    }

    /**
     * Both scopes are global now, and the route binding already answers to
     * them. This says the same thing a second time, in the controller, because
     * someone looking at one branch has no business reading another branch's
     * receipts and a file stream is the wrong place to trust a single check.
     */
    private function ensureVisible(Expense $expense): void
    {
        // SuperAdmin requests never bind an academy; skip rather than throw a
        // BindingResolutionException at them.
        if (app()->has('current_academy')) {
            abort_unless($expense->academy_id === app('current_academy')->id, 403);
        }

        $branchId = app(BranchContext::class)->branchId();

        // No `&& $expense->branch_id` term: an expense is incurred by exactly
        // one branch, so a null there is a data bug, and letting it through for
        // every branch is the wrong way for a guard to fail. Expense carries
        // BranchScope, so the binding normally 404s before this runs — this is
        // the belt behind that brace, and a belt must not fail open.
        abort_if($branchId && (int) $expense->branch_id !== $branchId, 403);
    }
}
