<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Acta;
use App\Support\HistorialLogger;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Str;
use OpenSpout\Common\Entity\Row;
use OpenSpout\Common\Entity\Style\Style;
use OpenSpout\Writer\XLSX\Writer as XLSXWriter;

class ActaController extends Controller
{
    public function index(Request $request)
    {
        $query = Acta::query()->with('reunion');

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

        return response()->json($query->orderByDesc('fecha_subida')->limit(500)->get());
    }

    public function export(Request $request)
    {
        $query = Acta::query()->with('reunion');

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

        $fileName = 'actas.xlsx';

        return response()->streamDownload(function () use ($query) {
            $writer = new XLSXWriter();
            $writer->openToFile('php://output');
            $writer->getCurrentSheet()->setName('Actas');

            $headerStyle = (new Style())->setFontBold();
            $writer->addRow(Row::fromValues([
                'ID',
                'Reunión ID',
                'Reunión (tipo)',
                'Reunión (fecha)',
                'Archivo',
                'Tipo',
                'Ruta',
                'Hash SHA256',
                'Fecha de subida',
            ], $headerStyle));

            $query->orderByDesc('fecha_subida')->chunk(500, function ($rows) use ($writer) {
                foreach ($rows as $row) {
                    $writer->addRow(Row::fromValues([
                        (string) ($row->id_acta ?? ''),
                        (string) ($row->id_reunion ?? ''),
                        (string) ($row->reunion?->tipo_reunion ?? ''),
                        (string) ($row->reunion?->fecha ?? ''),
                        (string) ($row->nombre_archivo ?? ''),
                        (string) ($row->tipo_archivo ?? ''),
                        (string) ($row->ruta_archivo ?? ''),
                        (string) ($row->hash_sha256 ?? ''),
                        (string) ($row->fecha_subida ?? ''),
                    ]));
                }
            });

            $writer->close();
        }, $fileName, ['Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']);
    }

    public function upload(Request $request)
    {
        $data = $request->validate([
            'id_reunion' => ['required', 'integer', 'exists:reuniones,id_reunion'],
            'archivo' => ['required', 'file', 'max:10240', 'mimes:pdf,doc,docx'],
            'nombre_archivo' => ['sometimes', 'nullable', 'string', 'max:255'],
        ]);

        /** @var UploadedFile $file */
        $file = $request->file('archivo');
        $idReunion = (int) $data['id_reunion'];

        $originalName = (string) $file->getClientOriginalName();
        $displayName = array_key_exists('nombre_archivo', $data) && is_string($data['nombre_archivo']) && trim($data['nombre_archivo']) !== ''
            ? trim($data['nombre_archivo'])
            : $originalName;

        $ext = strtolower((string) $file->getClientOriginalExtension());
        if ($ext === '') {
            $ext = 'bin';
        }

        $storedName = (string) Str::uuid().'.'.$ext;
        $relativeDir = 'private/actas/'.$idReunion;
        $relativePath = $relativeDir.'/'.$storedName;
        $absoluteDir = storage_path('app'.DIRECTORY_SEPARATOR.$relativeDir);

        if (! is_dir($absoluteDir)) {
            mkdir($absoluteDir, 0755, true);
        }

        $absolutePath = $absoluteDir.DIRECTORY_SEPARATOR.$storedName;
        $file->move($absoluteDir, $storedName);

        $hash = null;
        try {
            $hash = hash_file('sha256', $absolutePath) ?: null;
        } catch (\Throwable) {
            $hash = null;
        }

        $mime = (string) ($file->getClientMimeType() ?: $file->getMimeType() ?: '');
        if ($mime === '') {
            $mime = 'application/octet-stream';
        }

        $acta = Acta::create([
            'id_reunion' => $idReunion,
            'nombre_archivo' => $displayName,
            'tipo_archivo' => $mime,
            'ruta_archivo' => $relativePath,
            'hash_sha256' => $hash,
            'fecha_subida' => now(),
        ]);

        HistorialLogger::log(
            $request->user()?->getKey(),
            'acta_subida',
            'actas',
            (string) $acta->getKey(),
            'Subida de acta',
            null,
            $acta->toArray()
        );

        return response()->json($acta->load('reunion'), 201);
    }

    public function store(Request $request)
    {
        $data = $request->validate([
            'id_reunion' => ['required', 'integer', 'exists:reuniones,id_reunion'],
            'nombre_archivo' => ['required', 'string', 'max:255'],
            'tipo_archivo' => ['required', 'string', 'max:100'],
            'ruta_archivo' => ['required', 'string', 'max:500'],
            'hash_sha256' => ['nullable', 'string', 'size:64'],
        ]);

        $ruta = (string) $data['ruta_archivo'];
        $idReunion = (int) $data['id_reunion'];
        $normalized = ltrim($ruta, '/');
        if (str_contains($normalized, '..')) {
            return response()->json(['message' => 'Ruta de archivo inválida.'], 422);
        }

        $allowedPrefixes = [
            'private/actas/'.$idReunion.'/',
            'uploads/actas/'.$idReunion.'/',
        ];
        $allowed = false;
        foreach ($allowedPrefixes as $prefix) {
            if (str_starts_with($normalized, $prefix)) {
                $allowed = true;
                break;
            }
        }
        if (! $allowed) {
            return response()->json(['message' => 'Ruta de archivo inválida.'], 422);
        }

        $absolutePath = $this->resolveAbsoluteFilePath($ruta);
        if (! is_string($absolutePath) || $absolutePath === '' || ! is_file($absolutePath)) {
            return response()->json(['message' => 'Archivo no encontrado.'], 422);
        }

        $acta = Acta::create($data);

        HistorialLogger::log(
            $request->user()?->getKey(),
            'acta_creada',
            'actas',
            (string) $acta->getKey(),
            'Registro de acta',
            null,
            $acta->toArray()
        );

        return response()->json($acta->load('reunion'), 201);
    }

    public function show(Acta $acta)
    {
        return response()->json($acta->load('reunion'));
    }

    public function download(Request $request, Acta $acta)
    {
        $ruta = (string) ($acta->ruta_archivo ?? '');
        $absolutePath = $this->resolveAbsoluteFilePath($ruta);
        if (! is_string($absolutePath) || $absolutePath === '' || ! is_file($absolutePath)) {
            return response()->json(['message' => 'Archivo no encontrado.'], 404);
        }

        $fileName = trim((string) ($acta->nombre_archivo ?? 'acta'));
        if ($fileName === '') {
            $fileName = 'acta';
        }
        $fileName = str_replace(["\r", "\n"], ' ', $fileName);

        return response()->download($absolutePath, $fileName, [
            'X-Content-Type-Options' => 'nosniff',
        ]);
    }

    public function update(Request $request, Acta $acta)
    {
        $antes = $acta->toArray();

        $data = $request->validate([
            'id_reunion' => ['sometimes', 'required', 'integer', 'exists:reuniones,id_reunion'],
            'nombre_archivo' => ['sometimes', 'required', 'string', 'max:255'],
            'tipo_archivo' => ['sometimes', 'required', 'string', 'max:100'],
            'hash_sha256' => ['sometimes', 'nullable', 'string', 'size:64'],
        ]);

        $acta->fill($data)->save();

        HistorialLogger::log(
            $request->user()?->getKey(),
            'acta_actualizada',
            'actas',
            (string) $acta->getKey(),
            'Actualización de acta',
            $antes,
            $acta->toArray()
        );

        return response()->json($acta->load('reunion'));
    }

    public function destroy(Request $request, Acta $acta)
    {
        $antes = $acta->toArray();

        $ruta = (string) $acta->ruta_archivo;
        $absolutePath = $this->resolveAbsoluteFilePath($ruta);
        if (is_string($absolutePath) && $absolutePath !== '' && is_file($absolutePath)) {
            try {
                @unlink($absolutePath);
            } catch (\Throwable) {
            }
        }

        $acta->delete();

        HistorialLogger::log(
            $request->user()?->getKey(),
            'acta_eliminada',
            'actas',
            (string) ($antes['id_acta'] ?? $acta->getKey()),
            'Eliminación de acta',
            $antes,
            null
        );

        return response()->json(null, 204);
    }

    private function resolveAbsoluteFilePath(string $ruta): ?string
    {
        $ruta = trim($ruta);
        if ($ruta === '') {
            return null;
        }

        if (str_starts_with($ruta, '/uploads/')) {
            $candidate = public_path(ltrim($ruta, '/'));
            $realCandidate = realpath($candidate);
            $realBase = realpath(public_path('uploads'));
            if (is_string($realCandidate) && is_string($realBase) && str_starts_with($realCandidate, $realBase)) {
                return $realCandidate;
            }

            return null;
        }

        $normalized = ltrim($ruta, '/');
        $candidate = storage_path('app'.DIRECTORY_SEPARATOR.$normalized);
        $realCandidate = realpath($candidate);
        $realBase = realpath(storage_path('app'.DIRECTORY_SEPARATOR.'private'));
        if (is_string($realCandidate) && is_string($realBase) && str_starts_with($realCandidate, $realBase)) {
            return $realCandidate;
        }

        return null;
    }
}
