<?php

namespace App\Http\Controllers\Web;

use App\Http\Controllers\Controller;
use App\Models\Document;
use App\Support\FormTextRules;
use App\Support\ResolvesPrivateUploads;
use Carbon\Carbon;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;

class DocumentController extends Controller
{
    use ResolvesPrivateUploads;
    public function dashboard()
    {
        $query = $this->baseVisibleQuery();

        $kpis = [
            'total' => (clone $query)->count(),
            'active' => (clone $query)->where('status', 'active')->count(),
            'expiring_soon' => (clone $query)
                ->whereDate('expires_on', '>=', now()->toDateString())
                ->whereDate('expires_on', '<=', now()->addDays(30)->toDateString())
                ->count(),
            'expired' => (clone $query)->whereDate('expires_on', '<', now()->toDateString())->count(),
            'mandatory_missing' => $this->mandatoryMissingCount(),
        ];

        $expiringSoon = (clone $query)
            ->with('uploader')
            ->whereDate('expires_on', '>=', now()->toDateString())
            ->whereDate('expires_on', '<=', now()->addDays(30)->toDateString())
            ->orderBy('expires_on')
            ->limit(8)
            ->get();

        $recentDocuments = (clone $query)
            ->with('uploader')
            ->orderByDesc('created_at')
            ->limit(8)
            ->get();

        return view('documents.dashboard', compact('kpis', 'expiringSoon', 'recentDocuments'));
    }

    public function index()
    {
        $filters = [
            'search' => (string) request('search', ''),
            'category' => (string) request('category', ''),
            'status' => (string) request('status', ''),
            'visibility' => (string) request('visibility', ''),
            'from' => (string) request('from', ''),
            'to' => (string) request('to', ''),
        ];

        $documents = $this->baseVisibleQuery()
            ->with('uploader')
            ->search($filters['search'])
            ->filterCategory($filters['category'])
            ->filterStatus($filters['status'])
            ->filterVisibility($filters['visibility'])
            ->when($filters['from'] !== '', fn ($q) => $q->whereDate('created_at', '>=', $filters['from']))
            ->when($filters['to'] !== '', fn ($q) => $q->whereDate('created_at', '<=', $filters['to']))
            ->orderByDesc('created_at')
            ->paginate(15)
            ->withQueryString();

        $categories = config('employee_documents.upload_categories', []);
        $statusOptions = ['active', 'expiring_soon', 'expired', 'archived'];
        $visibilityOptions = ['company' => 'Company', 'private' => 'Private', 'team' => 'Team'];

        return view('documents.index', compact('documents', 'filters', 'categories', 'statusOptions', 'visibilityOptions'));
    }

    public function create()
    {
        $categories = config('employee_documents.upload_categories', []);
        $visibilityOptions = ['company' => 'Company', 'private' => 'Private', 'team' => 'Team'];
        /** @var User $user */
        $user = request()->user();
        $canAssignToEmployees = $user->canManageEmployeeDocuments();
        $employees = $canAssignToEmployees
            ? User::query()
                ->where('company_id', $user->company_id)
                ->forActiveDirectory()
                ->orderBy('name')
                ->get(['id', 'name', 'employee_number'])
            : collect();

        return view('documents.create', compact('categories', 'visibilityOptions', 'employees', 'canAssignToEmployees'));
    }

    public function store(Request $request)
    {
        $this->ensureCanCreate();

        $categoryKeys = implode(',', array_keys(config('employee_documents.upload_categories', [])));
        $validated = $request->validate([
            'name' => ['required', 'string', 'max:191', 'regex:/^(?=.*[A-Za-z])[A-Za-z\\s\\-_.\\/&()]+$/'],
            'category' => "required|string|in:{$categoryKeys}",
            'employee_id' => 'nullable|integer|exists:users,id',
            'type' => 'nullable|string|max:50',
            'document_number' => ['nullable', 'string', 'max:100', 'regex:/^[0-9][0-9\\/-]*$/'],
            'issued_on' => 'nullable|date|before_or_equal:today',
            'expires_on' => 'nullable|date|after_or_equal:issued_on',
            'visibility' => 'nullable|in:company,private,team',
            'notes' => FormTextRules::notNumericOnly(false, 2000),
            'is_mandatory' => 'nullable|boolean',
            'document' => 'required|file|max:10240|mimes:pdf,doc,docx,xls,xlsx,txt,png,jpg,jpeg',
        ], FormTextRules::messages());
        $file = $request->file('document');
        /** @var User $user */
        $user = $request->user();
        $owner = $user;
        if (! empty($validated['employee_id'])) {
            $owner = User::query()
                ->where('company_id', $user->company_id)
                ->forActiveDirectory()
                ->findOrFail((int) $validated['employee_id']);
            abort_unless($user->canViewFullEmployeeProfile($owner), 403, 'You cannot upload documents for this employee.');
        }

        $path = $file->store('documents/' . ($user->company_id ?? 'shared'), $this->privateUploadDisk());
        Document::create([
            'company_id' => $user->company_id,
            'name' => $validated['name'],
            'type' => $validated['category'],
            'category' => $validated['category'],
            'document_number' => $validated['document_number'] ?? null,
            'issued_on' => $validated['issued_on'] ?? null,
            'expires_on' => $validated['expires_on'] ?? null,
            'status' => $this->resolveStatusFromDates($validated['expires_on'] ?? null),
            'visibility' => $validated['visibility'] ?? 'company',
            'is_mandatory' => (bool) ($validated['is_mandatory'] ?? false),
            'notes' => $validated['notes'] ?? null,
            'path' => $path,
            'documentable_type' => User::class,
            'documentable_id' => $owner->id,
            'uploaded_by' => $user->id,
        ]);
        return redirect()->route('documents.index')->with('success', 'Document uploaded successfully.');
    }

    public function download(Document $document): BinaryFileResponse
    {
        $this->authorizeDocumentView($document);

        abort_if(! $this->privateUploadExists($document->path), 404);

        return response()->download($this->privateUploadAbsolutePath($document->path), basename($document->path));
    }

    public function destroy(Document $document)
    {
        $this->ensureCanManageDocument($document);
        $this->deletePrivateUpload($document->path);
        $document->delete();
        $redirect = request()->query('redirect', route('documents.index'));
        return redirect($redirect)->with('success', 'Document deleted.');
    }

    private function baseVisibleQuery(): \Illuminate\Database\Eloquent\Builder
    {
        /** @var User|null $user */
        $user = request()->user();
        $query = Document::query();
        if (! $user) {
            return $query->whereRaw('1=0');
        }

        if ($user->company_id) {
            $query->where('company_id', $user->company_id);
        }

        // Admin/HR: company-wide. Managers: reporting tree. Staff: own docs only.
        if ($user->canManageEmployeeDocuments()) {
            return $query;
        }

        $visibleIds = $user->visibleStaffUserIds();

        return $query->where(function ($q) use ($user, $visibleIds): void {
            $q->where(function ($own) use ($visibleIds): void {
                $own->where('documentable_type', User::class)
                    ->whereIn('documentable_id', $visibleIds);
            })->orWhere('uploaded_by', $user->id);
        });
    }

    private function ensureCanCreate(): void
    {
        /** @var User|null $user */
        $user = request()->user();
        abort_unless($user && ($user->canManageEmployeeDocuments() || $user->hasPermission('documents_create')), 403);
    }

    private function ensureCanManageDocument(Document $document): void
    {
        /** @var User|null $user */
        $user = request()->user();
        abort_unless($user, 403);
        if ($user->company_id && $document->company_id !== $user->company_id) {
            abort(403);
        }
        $isUploader = (int) $document->uploaded_by === (int) $user->id;
        abort_unless(
            $user->canManageEmployeeDocuments()
            || $user->hasPermission('documents_edit')
            || $isUploader,
            403
        );
    }

    private function authorizeDocumentView(Document $document): void
    {
        /** @var User|null $user */
        $user = request()->user();
        abort_unless($user, 403);
        if ($user->company_id && $document->company_id !== $user->company_id) {
            abort(403);
        }
        if ($user->canManageEmployeeDocuments()) {
            return;
        }
        $isOwnDocument = (string) $document->documentable_type === User::class
            && (int) $document->documentable_id === (int) $user->id;
        $isUploader = (int) $document->uploaded_by === (int) $user->id;
        $isTeamDocument = (string) $document->documentable_type === User::class
            && in_array((int) $document->documentable_id, $user->visibleStaffUserIds(), true);
        abort_unless($isOwnDocument || $isUploader || $isTeamDocument, 403);
    }

    private function resolveStatusFromDates(?string $expiresOn): string
    {
        if (! $expiresOn) {
            return 'active';
        }

        $expiry = Carbon::parse($expiresOn);
        if ($expiry->isPast()) {
            return 'expired';
        }
        if ($expiry->lte(now()->addDays(30))) {
            return 'expiring_soon';
        }
        return 'active';
    }

    private function mandatoryMissingCount(): int
    {
        /** @var User|null $user */
        $user = request()->user();
        if (! $user?->company_id) {
            return 0;
        }

        $mandatory = collect(config('employee_documents.mandatory', []));
        if ($mandatory->isEmpty()) {
            return 0;
        }

        $uploaded = $this->baseVisibleQuery()
            ->select(['category', 'type', 'name'])
            ->get();

        $uploadedTokens = $uploaded
            ->flatMap(function (Document $doc) {
                return [
                    strtolower((string) $doc->category),
                    strtolower((string) $doc->type),
                    strtolower((string) $doc->name),
                ];
            })
            ->filter()
            ->values()
            ->all();

        $missing = 0;
        foreach ($mandatory as $key => $meta) {
            $needles = array_merge([$key], (array) ($meta['aliases'] ?? []));
            $present = false;
            foreach ($needles as $needle) {
                $needle = strtolower((string) $needle);
                if (collect($uploadedTokens)->contains(fn (string $token) => str_contains($token, $needle))) {
                    $present = true;
                    break;
                }
            }
            if (! $present) {
                $missing++;
            }
        }
        return $missing;
    }
}
