<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Models\AppSetting;
use App\Models\Documento;
use App\Models\DocumentoCartella;
use App\Models\Individuo;
use App\Models\Gruppo;
use App\Models\Evento;
use App\Models\StorageRepository;
use App\Models\TipologiaDocumento;
use App\Services\StorageRepositoryService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class DocumentoController extends Controller
{
    public function __construct(
        private readonly StorageRepositoryService $repoService
    ) {}

    private function resolveStorageDisk(?Documento $documento = null): string
    {
        if ($documento && $documento->repository_id) {
            return 'local';
        }
        if ($documento && $documento->storage_disk) {
            return $documento->storage_disk;
        }
        return AppSetting::getDocumentiStorageDisk();
    }

    public function index(Request $request)
    {
        $this->authorizeRead('documenti');

        $folderId = $request->get('folder_id');
        $repoId = $request->get('repository_id');

        $query = Documento::with(['user', 'target', 'cartella', 'repository'])
            ->orderByDesc('created_at');

        if ($repoId) {
            $query->where('repository_id', $repoId);
        } elseif ($folderId) {
            $query->where('cartella_id', $folderId);
        } else {
            $query->whereNull('repository_id');
        }

        $documenti = $query->paginate(20);
        $cartelle = DocumentoCartella::with('children')
            ->whereNull('parent_id')
            ->orderBy('nome')
            ->get();

        $currentFolder = $folderId ? DocumentoCartella::find($folderId) : null;
        $breadcrumb = $currentFolder ? $currentFolder->ancestors() : [];

        $sottoCartelle = $folderId
            ? DocumentoCartella::where('parent_id', $currentFolder->id)->orderBy('nome')->get()
            : DocumentoCartella::whereNull('parent_id')->orderBy('nome')->get();

        $currentRepo = $repoId ? StorageRepository::find($repoId) : null;
        $repositories = StorageRepository::attivi()->get();

        $individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
        $gruppi = Gruppo::orderBy('nome')->get();
        $eventi = Evento::orderBy('nome_evento')->get();
        $mailingLists = \App\Models\MailingList::orderBy('nome')->get();
        $tipologie = TipologiaDocumento::attive();

        $cartelleMoveOptions = DocumentoCartella::whereNull('parent_id')
            ->with('children')
            ->orderBy('nome')
            ->get()
            ->map(fn($f) => [
                'id' => $f->id,
                'nome' => $f->nome,
                'children' => $f->children->map(fn($c) => ['id' => $c->id, 'nome' => $c->nome]),
            ]);

        return view('documenti.index', compact(
            'documenti', 'cartelle', 'currentFolder', 'breadcrumb', 'sottoCartelle',
            'currentRepo', 'repositories',
            'individui', 'gruppi', 'eventi', 'mailingLists', 'tipologie',
            'cartelleMoveOptions'
        ));
    }

    public function create()
    {
        $this->authorizeWrite('documenti');
        $individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
        $gruppi = Gruppo::orderBy('nome')->get();
        $eventi = Evento::orderBy('nome_evento')->get();
        $tipologie = TipologiaDocumento::attive();
        return view('documenti.create', compact('individui', 'gruppi', 'eventi', 'tipologie'));
    }

    public function edit($documento)
    {
        $this->authorizeWrite('documenti');
        $documento = Documento::with('target')->findOrFail($documento);
        $individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
        $gruppi = Gruppo::orderBy('nome')->get();
        $eventi = Evento::orderBy('nome_evento')->get();
        $mailingLists = \App\Models\MailingList::orderBy('nome')->get();
        $tipologie = TipologiaDocumento::attive();
        return view('documenti.edit', compact('documento', 'individui', 'gruppi', 'eventi', 'mailingLists', 'tipologie'));
    }

    public function options(Request $request)
    {
        $tipo = $request->get('tipo');
        $options = '';

        switch ($tipo) {
            case 'individuo':
                $items = Individuo::orderBy('cognome')->orderBy('nome')->get();
                foreach ($items as $item) {
                    $options .= '<option value="' . $item->id . '">' . e($item->cognome . ' ' . $item->nome) . '</option>';
                }
                break;
            case 'gruppo':
                $items = Gruppo::orderBy('nome')->get();
                foreach ($items as $item) {
                    $options .= '<option value="' . $item->id . '">' . e($item->nome) . '</option>';
                }
                break;
            case 'evento':
                $items = Evento::orderBy('nome_evento')->get();
                foreach ($items as $item) {
                    $options .= '<option value="' . $item->id . '">' . e($item->nome_evento) . '</option>';
                }
                break;
            case 'mailing':
                $items = \App\Models\MailingList::orderBy('nome')->get();
                foreach ($items as $item) {
                    $options .= '<option value="' . $item->id . '">' . e($item->nome) . '</option>';
                }
                break;
        }

        return response($options);
    }

    public function update(Request $request, $documento)
    {
        $this->authorizeWrite('documenti');
        $documento = Documento::findOrFail($documento);

        $tipologieValidi = TipologiaDocumento::opzioni();
        $tipologieRule = 'in:' . implode(',', $tipologieValidi);

        $data = $request->validate([
            'nome_file' => 'required|string|max:255',
            'tipologia' => 'required|' . $tipologieRule,
            'visibilita' => 'nullable|string',
            'visibilita_target_id' => 'nullable|integer',
            'visibilita_target_type' => 'nullable|string',
            'note' => 'nullable|string',
        ]);

        $data['visibilita'] = $request->contesto_tipo ?: 'pubblico';

        if ($request->contesto_tipo) {
            $typeMap = [
                'individuo' => Individuo::class,
                'gruppo' => Gruppo::class,
                'evento' => Evento::class,
                'mailing' => \App\Models\MailingList::class,
            ];
            $data['visibilita_target_type'] = $typeMap[$request->contesto_tipo] ?? null;
        } else {
            $data['visibilita'] = 'pubblico';
            $data['visibilita_target_id'] = null;
            $data['visibilita_target_type'] = null;
        }

        $documento->update($data);

        return redirect('/documenti')->with('success', 'Documento aggiornato.');
    }

    public function store(Request $request)
    {
        $this->authorizeWrite('documenti');
        $data = $request->validate([
            'nome_file' => 'required|string|max:255',
            'tipologia' => 'required|in:avatar,galleria,documento,statuto,altro',
            'file' => 'required|file|max:10240',
            'visibilita' => 'required|in:pubblico,individuo,gruppo',
            'visibilita_target_id' => 'nullable|integer',
            'visibilita_target_type' => 'nullable|string',
            'cartella_id' => 'nullable|integer|exists:documenti_cartelle,id',
            'repository_id' => 'nullable|integer|exists:storage_repositories,id',
        ]);

        $repositoryId = $data['repository_id'] ?? null;

        if ($repositoryId) {
            $repo = StorageRepository::findOrFail($repositoryId);
            $filesystem = $this->repoService->buildFilesystem($repo);
            if (!$filesystem) {
                return back()->with('error', 'Impossibile connettersi al repository remoto.');
            }

            $extension = $request->file('file')->getClientOriginalExtension();
            $filename = $data['nome_file'] . '.' . ($extension ?: $request->file('file')->guessExtension());
            $path = 'documents/' . date('Y/m/d') . '/' . $filename;

            $stream = fopen($request->file('file')->getRealPath(), 'rb');
            $filesystem->writeStream($path, $stream);
            fclose($stream);

            $filePath = $path;
        } else {
            $disk = $this->resolveStorageDisk();
            $storagePath = AppSetting::getDocumentiStoragePath();
            $filePath = $request->file('file')->store($storagePath, $disk);
        }

        $visibilitaTargetType = null;
        if ($data['visibilita'] === 'individuo' && $data['visibilita_target_id']) {
            $visibilitaTargetType = Individuo::class;
        } elseif ($data['visibilita'] === 'gruppo' && $data['visibilita_target_id']) {
            $visibilitaTargetType = Gruppo::class;
        }

        Documento::create([
            'nome_file' => $data['nome_file'],
            'file_path' => $filePath,
            'storage_disk' => $disk ?? null,
            'tipo' => 'upload',
            'tipologia' => $data['tipologia'],
            'visibilita' => $data['visibilita'],
            'visibilita_target_id' => $data['visibilita_target_id'] ?? null,
            'visibilita_target_type' => $visibilitaTargetType,
            'mime_type' => $request->file('file')->getMimeType(),
            'dimensione' => $request->file('file')->getSize(),
            'user_id' => auth()->id(),
            'cartella_id' => $data['cartella_id'] ?? null,
            'repository_id' => $repositoryId,
        ]);

        $redirect = $request->_redirect ?? url()->previous();
        return redirect($redirect)->with('success', 'Documento caricato.');
    }

    public function download($documento)
    {
        $this->authorizeRead('documenti');
        $documento = Documento::findOrFail($documento);

        $extension = pathinfo($documento->file_path, PATHINFO_EXTENSION);
        $filename = $extension ? $documento->nome_file . '.' . $extension : $documento->nome_file;

        if ($documento->repository_id) {
            $repo = StorageRepository::find($documento->repository_id);
            if (!$repo) {
                abort(404);
            }
            $filesystem = $this->repoService->buildFilesystem($repo);
            if (!$filesystem || !$filesystem->fileExists($documento->file_path)) {
                abort(404);
            }

            $stream = $filesystem->readStream($documento->file_path);
            if (!$stream) {
                abort(404);
            }

            return response()->stream(function () use ($stream) {
                fpassthru($stream);
                fclose($stream);
            }, 200, [
                'Content-Type' => $documento->mime_type ?: 'application/octet-stream',
                'Content-Disposition' => 'attachment; filename="' . $filename . '"',
                'Content-Length' => $documento->dimensione,
            ]);
        }

        $disk = $this->resolveStorageDisk($documento);
        if (!Storage::disk($disk)->exists($documento->file_path)) {
            abort(404);
        }

        return Storage::disk($disk)->download(
            $documento->file_path,
            $filename,
            ['Content-Type' => $documento->mime_type]
        );
    }

    public function preview($documento)
    {
        $this->authorizeRead('documenti');
        $documento = Documento::findOrFail($documento);

        $previewableMimeTypes = [
            'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml',
            'application/pdf',
            'text/plain', 'text/html', 'text/csv',
        ];

        if (!in_array($documento->mime_type, $previewableMimeTypes)) {
            return response()->view('documenti.preview-fallback', [
                'documento' => $documento,
            ])->header('X-Preview-Fallback', 'true');
        }

        if ($documento->repository_id) {
            $repo = StorageRepository::find($documento->repository_id);
            if (!$repo) {
                abort(404);
            }
            $filesystem = $this->repoService->buildFilesystem($repo);
            if (!$filesystem || !$filesystem->fileExists($documento->file_path)) {
                abort(404);
            }

            $stream = $filesystem->readStream($documento->file_path);
            if (!$stream) {
                abort(404);
            }

            return response()->stream(function () use ($stream) {
                fpassthru($stream);
                fclose($stream);
            }, 200, [
                'Content-Type' => $documento->mime_type,
                'Content-Disposition' => 'inline; filename="' . basename($documento->file_path) . '"',
            ]);
        }

        $disk = $this->resolveStorageDisk($documento);
        if (!Storage::disk($disk)->exists($documento->file_path)) {
            abort(404);
        }

        $fullPath = Storage::disk($disk)->path($documento->file_path);
        return response()->file($fullPath, ['Content-Type' => $documento->mime_type]);
    }

    public function destroy($documento)
    {
        $this->authorizeDelete('documenti');
        $documento = Documento::findOrFail($documento);

        if ($documento->repository_id) {
            $repo = StorageRepository::find($documento->repository_id);
            if ($repo) {
                $filesystem = $this->repoService->buildFilesystem($repo);
                if ($filesystem && $filesystem->fileExists($documento->file_path)) {
                    $filesystem->delete($documento->file_path);
                }
            }
        } else {
            $disk = $this->resolveStorageDisk($documento);
            if (Storage::disk($disk)->exists($documento->file_path)) {
                Storage::disk($disk)->delete($documento->file_path);
            }
        }

        $documento->delete();

        $redirect = request('_redirect', url()->previous());
        return redirect($redirect)->with('success', 'Documento eliminato.');
    }

    public function checkLinks($documento)
    {
        $documento = Documento::findOrFail($documento);
        $links = [];

        if (in_array($documento->visibilita, ['individuo', 'gruppo', 'evento']) && $documento->visibilita_target_id) {
            if ($documento->visibilita === 'individuo') {
                $target = Individuo::find($documento->visibilita_target_id);
                if ($target) {
                    $links[] = [
                        'type' => 'individuo',
                        'name' => $target->cognome . ' ' . $target->nome,
                        'url' => '/individui/' . $target->id,
                    ];
                }
            } elseif ($documento->visibilita === 'gruppo') {
                $target = Gruppo::find($documento->visibilita_target_id);
                if ($target) {
                    $links[] = [
                        'type' => 'gruppo',
                        'name' => $target->nome,
                        'url' => '/gruppi/' . $target->id,
                    ];
                }
            } elseif ($documento->visibilita === 'evento') {
                $target = Evento::find($documento->visibilita_target_id);
                if ($target) {
                    $links[] = [
                        'type' => 'evento',
                        'name' => $target->nome_evento,
                        'url' => '/eventi/' . $target->id,
                    ];
                }
            }
        }

        if ($documento->tipologia === 'avatar') {
            $linkedToAvatar = Individuo::whereHas('avatar', function($q) use ($documento) {
                $q->where('id', $documento->id);
            })->count();
            if ($linkedToAvatar > 0) {
                $links[] = [
                    'type' => 'avatar',
                    'name' => 'Avatar di ' . $linkedToAvatar . ' individuo(i)',
                    'url' => null,
                ];
            }
        }

        if ($documento->eventi()->count() > 0) {
            foreach ($documento->eventi as $evento) {
                $links[] = [
                    'type' => 'evento',
                    'name' => 'Evento: ' . $evento->nome_evento,
                    'url' => '/eventi/' . $evento->id,
                ];
            }
        }

        return response()->json([
            'has_links' => count($links) > 0,
            'links' => $links,
        ]);
    }

    public function checkMassLinks(Request $request)
    {
        $idsInput = $request->input('ids', '');
        $ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);

        if (empty($ids)) {
            return response()->json(['linked_count' => 0, 'links' => []]);
        }

        $documenti = Documento::whereIn('id', $ids)->get();
        $allLinks = [];
        $linkedCount = 0;

        foreach ($documenti as $documento) {
            $isLinked = false;
            $linkName = '';
            $linkUrl = null;

            if (in_array($documento->visibilita, ['individuo', 'gruppo', 'evento']) && $documento->visibilita_target_id) {
                $isLinked = true;
                $linkName = $documento->nome_file . ' → ';

                if ($documento->visibilita === 'individuo') {
                    $target = Individuo::find($documento->visibilita_target_id);
                    $linkName .= $target ? $target->cognome . ' ' . $target->nome : 'Individuo #' . $documento->visibilita_target_id;
                    $linkUrl = $target ? '/individui/' . $target->id : null;
                } elseif ($documento->visibilita === 'gruppo') {
                    $target = Gruppo::find($documento->visibilita_target_id);
                    $linkName .= $target ? $target->nome : 'Gruppo #' . $documento->visibilita_target_id;
                    $linkUrl = $target ? '/gruppi/' . $target->id : null;
                } elseif ($documento->visibilita === 'evento') {
                    $target = Evento::find($documento->visibilita_target_id);
                    $linkName .= $target ? $target->nome_evento : 'Evento #' . $documento->visibilita_target_id;
                    $linkUrl = $target ? '/eventi/' . $target->id : null;
                }
            }

            if ($documento->tipologia === 'avatar') {
                $linkedToAvatar = Individuo::whereHas('avatar', function($q) use ($documento) {
                    $q->where('id', $documento->id);
                })->count();
                if ($linkedToAvatar > 0) {
                    $isLinked = true;
                    $allLinks[] = [
                        'name' => $documento->nome_file . ' → Avatar di ' . $linkedToAvatar . ' individuo(i)',
                        'url' => null,
                    ];
                    $linkedCount++;
                }
            }

            if ($documento->eventi()->count() > 0) {
                $eventi = $documento->eventi;
                foreach ($eventi as $evento) {
                    $allLinks[] = [
                        'name' => $documento->nome_file . ' → Evento: ' . $evento->nome_evento,
                        'url' => '/eventi/' . $evento->id,
                    ];
                    $linkedCount++;
                }
            }

            if ($isLinked && $documento->tipologia !== 'avatar' && $linkName) {
                $allLinks[] = [
                    'name' => $linkName,
                    'url' => $linkUrl,
                ];
                $linkedCount++;
            }
        }

        return response()->json([
            'linked_count' => $linkedCount,
            'links' => $allLinks,
        ]);
    }

    public function massDownload(Request $request)
    {
        $this->authorizeRead('documenti');
        $idsInput = $request->input('ids', '');
        $ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);

        if (empty($ids)) {
            return back()->with('error', 'Nessun documento selezionato.');
        }

        $documenti = Documento::whereIn('id', $ids)->get();

        if ($documenti->isEmpty()) {
            return back()->with('error', 'Nessun documento trovato.');
        }

        $zip = new \ZipArchive();
        $zipPath = tempnam(sys_get_temp_dir(), 'docs_') . '.zip';

        if ($zip->open($zipPath, \ZipArchive::CREATE) !== true) {
            return back()->with('error', 'Impossibile creare l\'archivio ZIP.');
        }

        $addedCount = 0;
        foreach ($documenti as $documento) {
            $extension = pathinfo($documento->file_path, PATHINFO_EXTENSION);
            $filename = $extension ? $documento->nome_file . '.' . $extension : $documento->nome_file;

            try {
                if ($documento->repository_id) {
                    $repo = StorageRepository::find($documento->repository_id);
                    if ($repo) {
                        $filesystem = $this->repoService->buildFilesystem($repo);
                        if ($filesystem && $filesystem->fileExists($documento->file_path)) {
                            $stream = $filesystem->readStream($documento->file_path);
                            if ($stream) {
                                $zip->addFromString($filename, stream_get_contents($stream));
                                fclose($stream);
                                $addedCount++;
                            }
                        }
                    }
                } else {
                    $disk = $this->resolveStorageDisk($documento);
                    if (Storage::disk($disk)->exists($documento->file_path)) {
                        $zip->addFile(Storage::disk($disk)->path($documento->file_path), $filename);
                        $addedCount++;
                    }
                }
            } catch (\Exception $e) {
                \Illuminate\Support\Facades\Log::warning('ZIP download skipped file: ' . $documento->id . ' - ' . $e->getMessage());
            }
        }

        $zip->close();

        if ($addedCount === 0) {
            unlink($zipPath);
            return back()->with('error', 'Nessun file disponibile per il download.');
        }

        return response()->download($zipPath, 'documenti.zip')->deleteFileAfterSend(true);
    }

    public function massDestroy(Request $request)
    {
        $this->authorizeDelete('documenti');
        $idsInput = $request->input('ids', '');
        $ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);

        if (empty($ids)) {
            return back()->with('error', 'Nessun documento selezionato.');
        }

        $documenti = Documento::whereIn('id', $ids)->get();

        foreach ($documenti as $documento) {
            if ($documento->repository_id) {
                $repo = StorageRepository::find($documento->repository_id);
                if ($repo) {
                    $filesystem = $this->repoService->buildFilesystem($repo);
                    if ($filesystem && $filesystem->fileExists($documento->file_path)) {
                        $filesystem->delete($documento->file_path);
                    }
                }
            } else {
                $disk = $this->resolveStorageDisk($documento);
                if (Storage::disk($disk)->exists($documento->file_path)) {
                    Storage::disk($disk)->delete($documento->file_path);
                }
            }
            $documento->delete();
        }

        return back()->with('success', count($ids) . ' documenti eliminati.');
    }

    public function massUpdate(Request $request)
    {
        $this->authorizeWrite('documenti');
        $idsInput = $request->input('ids', '');
        $ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);

        if (empty($ids)) {
            return back()->with('error', 'Nessun documento selezionato.');
        }

        $data = $request->validate([
            'tipologia' => 'nullable|string',
            'visibilita' => 'nullable|string',
            'visibilita_target_id' => 'nullable|integer',
            'visibilita_target_type' => 'nullable|string',
        ]);

        $tipologia = $data['tipologia'] ?? null;
        $visibilita = $data['visibilita'] ?? null;

        if (empty($tipologia) && empty($visibilita)) {
            return back()->with('error', 'Specificare almeno un campo da aggiornare.');
        }

        $updateData = [];
        if (!empty($tipologia)) {
            $updateData['tipologia'] = $tipologia;
        }
        if (!empty($visibilita)) {
            $updateData['visibilita'] = $visibilita;
            $typeMap = [
                'individuo' => Individuo::class,
                'gruppo' => Gruppo::class,
                'evento' => Evento::class,
                'mailing' => \App\Models\MailingList::class,
            ];
            if (isset($typeMap[$visibilita])) {
                $updateData['visibilita_target_type'] = $typeMap[$visibilita];
            }
        }

        if (!empty($data['visibilita_target_id']) && !empty($data['visibilita_target_type'])) {
            $typeMap = [
                'individuo' => Individuo::class,
                'gruppo' => Gruppo::class,
                'evento' => Evento::class,
                'mailing' => \App\Models\MailingList::class,
            ];
            $updateData['visibilita'] = $data['visibilita_target_type'];
            $updateData['visibilita_target_id'] = (int) $data['visibilita_target_id'];
            $updateData['visibilita_target_type'] = $typeMap[$data['visibilita_target_type']] ?? $data['visibilita_target_type'];
        }

        Documento::whereIn('id', $ids)->update($updateData);

        return back()->with('success', count($ids) . ' documenti aggiornati.');
    }

    public function massAssociate(Request $request)
    {
        $this->authorizeWrite('documenti');
        $idsInput = $request->input('ids', '');
        $ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);

        if (empty($ids)) {
            return back()->with('error', 'Nessun documento selezionato.');
        }

        $data = $request->validate([
            'target_type' => 'required|in:individuo,gruppo,evento,mailing',
            'target_id' => 'required|integer',
        ]);

        $targetType = match($data['target_type']) {
            'individuo' => Individuo::class,
            'gruppo' => Gruppo::class,
            'evento' => Evento::class,
            'mailing' => \App\Models\MailingList::class,
        };

        Documento::whereIn('id', $ids)->update([
            'visibilita' => $data['target_type'],
            'visibilita_target_id' => $data['target_id'],
            'visibilita_target_type' => $targetType,
        ]);

        return back()->with('success', count($ids) . ' documenti associati.');
    }

    public function massMove(Request $request): RedirectResponse
    {
        $this->authorizeWrite('documenti');
        $idsInput = $request->input('ids', '');
        $ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);

        if (empty($ids)) {
            return back()->with('error', 'Nessun documento selezionato.');
        }

        $data = $request->validate([
            'cartella_id' => 'nullable|integer|exists:documenti_cartelle,id',
        ]);

        Documento::whereIn('id', $ids)->update([
            'cartella_id' => $data['cartella_id'] ?? null,
        ]);

        return back()->with('success', count($ids) . ' documenti spostati.');
    }

    public function moveDocumento(Request $request, int $id): JsonResponse
    {
        $this->authorizeWrite('documenti');

        $documento = Documento::findOrFail($id);

        $data = $request->validate([
            'cartella_id' => 'nullable|integer|exists:documenti_cartelle,id',
        ]);

        $documento->update(['cartella_id' => $data['cartella_id'] ?? null]);

        return response()->json(['success' => true]);
    }
}
