<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Enums\AssetCondition;
use App\Enums\AssetDocumentType;
use App\Enums\AssetStatus;
use App\Enums\WarrantyType;
use App\Exports\AssetsExport;
use App\Http\Requests\Asset\StoreAssetRequest;
use App\Http\Requests\Asset\UpdateAssetRequest;
use App\Http\Requests\Asset\UpdateAssetStatusRequest;
use App\Models\Asset;
use App\Models\AssetCategory;
use App\Models\Location;
use App\Models\Team;
use App\Services\AssetDocumentService;
use App\Services\AssetService;
use App\Services\AssetTagGenerator;
use App\Services\WarrantyService;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection as SupportCollection;
use Illuminate\View\View;
use Maatwebsite\Excel\Facades\Excel;
use Symfony\Component\HttpFoundation\BinaryFileResponse;

/**
 * The asset register — the core object of the product.
 *
 * Field edits are plain Eloquent; tag generation and status transitions go
 * through AssetService, because those two are the only things a bare
 * `Asset::create()` / `$asset->update()` cannot do safely on their own.
 */
class AssetController extends Controller
{
    public function __construct(
        private readonly AssetService $assets,
        private readonly AssetTagGenerator $tagGenerator,
        private readonly WarrantyService $warranties,
        private readonly AssetDocumentService $documents,
    ) {}

    public function index(Request $request): View
    {
        $showingDeleted = $request->boolean('trashed');
        $filters = $this->filtersFrom($request);

        $assets = Asset::query()
            ->when($showingDeleted, fn ($query) => $query->onlyTrashed())
            ->with(['category.parent', 'location', 'team', 'currentEmployee'])
            ->filter($filters)
            ->orderBy('asset_tag')
            ->paginate((int) setting('pagination_per_page', config('ams.pagination.per_page')))
            ->withQueryString();

        return view('assets.index', [
            'assets' => $assets,
            'showingDeleted' => $showingDeleted,
            'deletedCount' => Asset::onlyTrashed()->count(),
            'filters' => $filters,
            // qualifiedName() reads $category->parent for a subcategory's
            // display label; eager-loading here keeps the view free of an
            // incidental query per row.
            'categories' => AssetCategory::query()->with('parent')->orderBy('name')->get(),
            'locations' => Location::query()->orderBy('name')->get(),
            'teams' => Team::query()->orderBy('name')->get(),
        ]);
    }

    public function create(): View
    {
        return view('assets.create', [
            'asset' => new Asset([
                'status' => AssetStatus::InStock,
                'condition' => AssetCondition::Good,
                'currency' => setting('default_currency', 'INR'),
                'is_active' => true,
            ]),
        ] + $this->formOptions());
    }

    public function store(StoreAssetRequest $request): RedirectResponse
    {
        $data = $request->validated();

        // Not Asset columns — WarrantyService::create() needs them, not
        // AssetService::create(), which would otherwise just ignore them as
        // unfillable.
        $warrantyStart = $data['warranty_start_date'] ?? null;
        $warrantyEnd = $data['warranty_end_date'] ?? null;
        unset($data['warranty_start_date'], $data['warranty_end_date']);

        // Likewise not an Asset column — the file itself never went through
        // $request->validated() as anything but an UploadedFile, so it is
        // read directly off the request rather than out of $data.
        $documentType = AssetDocumentType::tryFrom((string) ($data['document_type'] ?? '')) ?? AssetDocumentType::Invoice;
        unset($data['document_type'], $data['document']);

        $asset = $this->assets->create($data);

        if ($warrantyStart !== null && $warrantyEnd !== null) {
            $this->warranties->create($asset, [
                'type' => WarrantyType::Manufacturer,
                'start_date' => $warrantyStart,
                'end_date' => $warrantyEnd,
            ]);
        }

        // Validation already passed by this point, so the asset above is
        // guaranteed to exist before the file ever touches storage — a
        // rejected asset can never leave an orphaned document behind.
        if ($request->hasFile('document')) {
            /** @var UploadedFile $document */
            $document = $request->file('document');
            $this->documents->attach($asset, $document, $documentType);
        }

        return redirect()
            ->route('assets.show', $asset)
            ->with('success', __('Asset ":tag" added.', ['tag' => $asset->asset_tag]));
    }

    public function show(Asset $asset): View
    {
        $asset->load([
            'category.parent',
            'location',
            'team',
            'creator',
            'updater',
            'currentEmployee',
            'currentAssignment',
            'assignments.employee',
            'assignments.assignedBy',
            'assignments.receivedBy',
            'primaryWarranty',
            'warranties',
            'documents.uploadedBy',
        ]);

        return view('assets.show', [
            'asset' => $asset,
            // A short preview only. The full, paginated trail lives at
            // assets.history — see the route map in the architecture doc.
            'recentActivity' => $asset->auditLogs()->with('user')->limit(6)->get(),
        ]);
    }

    public function edit(Asset $asset): View
    {
        return view('assets.edit', [
            'asset' => $asset,
        ] + $this->formOptions());
    }

    public function update(UpdateAssetRequest $request, Asset $asset): RedirectResponse
    {
        $this->assets->update($asset, $request->validated());

        return redirect()
            ->route('assets.show', $asset)
            ->with('success', __('Asset ":tag" updated.', ['tag' => $asset->asset_tag]));
    }

    public function destroy(Asset $asset): RedirectResponse
    {
        $this->authorize('delete', $asset);

        $tag = $asset->asset_tag;

        $this->assets->delete($asset);

        return redirect()
            ->route('assets.index')
            ->with('success', __('Asset ":tag" deleted. You can restore it from the deleted list.', ['tag' => $tag]));
    }

    /**
     * Bring a soft-deleted asset back.
     */
    public function restore(Asset $asset): RedirectResponse
    {
        $asset->restore();

        return redirect()
            ->route('assets.index')
            ->with('success', __('Asset ":tag" restored.', ['tag' => $asset->asset_tag]));
    }

    /**
     * A single, deliberate status transition — never a free-form field edit.
     * See UpdateAssetStatusRequest and AssetService::changeStatus().
     */
    public function updateStatus(UpdateAssetStatusRequest $request, Asset $asset): RedirectResponse
    {
        $target = AssetStatus::from((string) $request->validated()['status']);

        $this->assets->changeStatus($asset, $target);

        return redirect()
            ->route('assets.show', $asset)
            ->with('success', __('Status changed to ":status".', ['status' => $target->label()]));
    }

    /**
     * The full, paginated activity trail for one asset — everything the
     * generic Auditable trail knows today (created, edited, status changes,
     * deleted, restored), and the same feed Phase 5 assignment/return events
     * will land in without this page needing to change.
     */
    public function history(Asset $asset): View
    {
        return view('assets.history', [
            'asset' => $asset,
            'logs' => $asset->auditLogs()->with('user')->paginate(25),
        ]);
    }

    /**
     * Soft-delete a batch of selected assets in one action.
     *
     * Loads and deletes each row individually rather than a single bulk
     * `whereIn(...)->delete()` query: a query-builder delete bypasses Eloquent
     * model events entirely, which means the Auditable trait would never fire
     * and none of these deletions would appear in the audit trail.
     */
    public function bulkDestroy(Request $request): RedirectResponse
    {
        $ids = $this->idsFrom($request);

        if ($ids === []) {
            return redirect()->route('assets.index')->with('error', __('Select at least one asset first.'));
        }

        /** @var Collection<int, Asset> $selected */
        $selected = Asset::query()->whereIn('id', $ids)->get();

        $selected->each(fn (Asset $asset) => $asset->delete());

        return redirect()
            ->route('assets.index')
            ->with('success', trans_choice(
                ':count asset deleted.|:count assets deleted.',
                $selected->count(),
                ['count' => $selected->count()],
            ));
    }

    /**
     * What AssetTagGenerator::next() would currently produce for the given
     * category — a suggestion only, shown on the Add Asset form before the
     * asset exists. Never used to actually assign a tag; AssetService::create()
     * generates the real one at save time regardless of what this returned.
     */
    public function tagPreview(Request $request): JsonResponse
    {
        $categoryId = $request->integer('category_id');

        $category = $categoryId > 0
            ? AssetCategory::query()->find($categoryId)
            : AssetCategory::query()->orderBy('id')->first();

        if ($category === null) {
            return response()->json(['tag' => null]);
        }

        return response()->json(['tag' => $this->tagGenerator->preview($category)]);
    }

    /**
     * Download the asset register as an .xlsx file.
     *
     * With a row selection (the "Export Selected" bulk action) that selection
     * wins outright; otherwise the export honours whatever filters are
     * currently applied to the listing, so "export" always means "export what
     * I am looking at".
     */
    public function export(Request $request): BinaryFileResponse
    {
        $ids = $this->idsFrom($request);

        $export = new AssetsExport(
            filters: $this->filtersFrom($request),
            onlyIds: $ids === [] ? null : $ids,
        );

        return Excel::download($export, 'aumni-ams-assets-'.now()->format('Y-m-d-His').'.xlsx');
    }

    /**
     * @return array<string, mixed>
     */
    private function filtersFrom(Request $request): array
    {
        return [
            'search' => $request->string('search')->toString(),
            'status' => $request->string('status')->toString(),
            'condition' => $request->string('condition')->toString(),
            'category_id' => $request->string('category_id')->toString(),
            'location_id' => $request->string('location_id')->toString(),
            'team_id' => $request->string('team_id')->toString(),
        ];
    }

    /**
     * @return array<int, int>
     */
    private function idsFrom(Request $request): array
    {
        /** @var array<int, mixed> $raw */
        $raw = (array) $request->input('asset_ids', []);

        return array_values(array_unique(array_filter(array_map('intval', $raw))));
    }

    /**
     * Reference data shared by the create and edit forms.
     *
     * @return array<string, mixed>
     */
    private function formOptions(): array
    {
        return [
            'categories' => AssetCategory::query()->active()->with('parent')->orderBy('name')->get(),
            'locations' => Location::query()->active()->orderBy('name')->get(),
            'teams' => Team::query()->active()->orderBy('name')->get(),
            'manufacturerSuggestions' => $this->distinct('manufacturer'),
            'vendorSuggestions' => $this->distinct('vendor'),
        ];
    }

    /**
     * @return SupportCollection<int, string>
     */
    private function distinct(string $column): SupportCollection
    {
        return Asset::query()
            ->whereNotNull($column)
            ->where($column, '!=', '')
            ->distinct()
            ->orderBy($column)
            ->pluck($column);
    }
}
