<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Institucion;
use App\Models\Sector;
use App\Support\HistorialLogger;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use OpenSpout\Common\Entity\Row;
use OpenSpout\Common\Entity\Style\Style;
use OpenSpout\Writer\XLSX\Writer as XLSXWriter;

class InstitucionController extends Controller
{
    public function index(Request $request)
    {
        $hasInstitucionPadre = Schema::hasTable('instituciones_padre') && Schema::hasColumn('instituciones', 'id_institucion_padre');

        $select = [
            'id_institucion',
            'nombre',
            'descripcion',
            'estado',
            'id_sector',
            'logo',
            'direccion',
            'ruc',
            'fecha_creacion',
            'fecha_actualizacion',
        ];

        if ($hasInstitucionPadre) {
            $select[] = 'id_institucion_padre';
        }

        $with = ['sector:id_sector,nombre_sector'];
        if ($hasInstitucionPadre) {
            $with[] = 'institucionPadre:id_institucion_padre,nombre,estado';
        }

        $query = Institucion::query()
            ->select($select)
            ->with($with)
            ->withCount(['representantes', 'documentos']);

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

        if ($request->filled('estado')) {
            $query->where('estado', $request->string('estado'));
        }

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

        if ($request->filled('q')) {
            $q = trim((string) $request->string('q'));
            if ($q !== '') {
                $query->where(function ($sub) use ($q) {
                    $sub
                        ->where('nombre', 'like', '%'.$q.'%')
                        ->orWhere('descripcion', 'like', '%'.$q.'%');
                });
            }
        }

        $perPage = (int) $request->integer('per_page', 20);
        if ($perPage < 1) {
            $perPage = 1;
        }
        if ($perPage > 100) {
            $perPage = 100;
        }

        $paginator = $query->orderBy('nombre')->paginate($perPage);

        return response()->json([
            'data' => $paginator->items(),
            'meta' => [
                'current_page' => $paginator->currentPage(),
                'per_page' => $paginator->perPage(),
                'last_page' => $paginator->lastPage(),
                'total' => $paginator->total(),
            ],
        ]);
    }

    public function store(Request $request)
    {
        $hasInstitucionPadre = Schema::hasTable('instituciones_padre') && Schema::hasColumn('instituciones', 'id_institucion_padre');

        $data = $request->validate([
            'nombre' => ['required', 'string', 'max:200', 'unique:instituciones,nombre'],
            'descripcion' => ['nullable', 'string'],
            'estado' => ['nullable', 'in:activo,inactivo'],
            'id_sector' => ['required', 'integer', 'exists:sectores,id_sector'],
            'id_institucion_padre' => $hasInstitucionPadre ? ['nullable', 'integer', 'exists:instituciones_padre,id_institucion_padre'] : ['nullable'],
            'logo' => ['nullable'],
            'direccion' => ['nullable', 'string', 'max:300'],
            'ruc' => ['nullable', 'string', 'max:20', 'unique:instituciones,ruc'],
        ]);

        $data['nombre'] = trim((string) $data['nombre']);
        if (array_key_exists('descripcion', $data) && is_string($data['descripcion'])) {
            $data['descripcion'] = trim($data['descripcion']);
            if ($data['descripcion'] === '') {
                $data['descripcion'] = null;
            }
        }

        if ($request->hasFile('logo')) {
            $request->validate([
                'logo' => ['file', 'max:3072', 'mimes:png,jpg,jpeg,webp'],
            ]);

            $file = $request->file('logo');
            $ext = strtolower((string) ($file->getClientOriginalExtension() ?: ''));
            if ($ext === '') {
                $ext = 'bin';
            }
            $name = Str::uuid().'.'.$ext;
            $absoluteDir = public_path('uploads/logos');
            if (! is_dir($absoluteDir)) {
                mkdir($absoluteDir, 0755, true);
            }
            $file->move($absoluteDir, $name);
            $data['logo'] = 'uploads/logos/'.$name;
        } elseif (array_key_exists('logo', $data) && is_string($data['logo'])) {
            $data['logo'] = trim($data['logo']);
            if ($data['logo'] === '') {
                $data['logo'] = null;
            }
        }

        if (array_key_exists('direccion', $data) && is_string($data['direccion'])) {
            $data['direccion'] = trim($data['direccion']);
            if ($data['direccion'] === '') {
                $data['direccion'] = null;
            }
        }
        if (array_key_exists('ruc', $data) && is_string($data['ruc'])) {
            $data['ruc'] = trim($data['ruc']);
            if ($data['ruc'] === '') {
                $data['ruc'] = null;
            }
        }

        $institucion = Institucion::create($data);

        HistorialLogger::log(
            $request->user()?->getKey(),
            'institucion_creada',
            'instituciones',
            (string) $institucion->getKey(),
            'Creación de organismo',
            null,
            $institucion->toArray()
        );

        $load = ['sector'];
        if ($hasInstitucionPadre) {
            $load[] = 'institucionPadre';
        }

        return response()->json($institucion->load($load)->loadCount(['representantes', 'documentos']), 201);
    }

    public function show(Institucion $institucion)
    {
        $hasInstitucionPadre = Schema::hasTable('instituciones_padre') && Schema::hasColumn('instituciones', 'id_institucion_padre');

        $load = ['sector', 'representantes', 'documentos'];
        if ($hasInstitucionPadre) {
            $load[] = 'institucionPadre';
        }

        return response()->json(
            $institucion
                ->load($load)
                ->loadCount(['representantes', 'documentos'])
        );
    }

    public function update(Request $request, Institucion $institucion)
    {
        $hasInstitucionPadre = Schema::hasTable('instituciones_padre') && Schema::hasColumn('instituciones', 'id_institucion_padre');

        $antes = $institucion->loadCount(['representantes', 'documentos'])->toArray();

        $data = $request->validate([
            'nombre' => ['sometimes', 'required', 'string', 'max:200', 'unique:instituciones,nombre,'.$institucion->getKey().',id_institucion'],
            'descripcion' => ['sometimes', 'nullable', 'string'],
            'estado' => ['sometimes', 'nullable', 'in:activo,inactivo'],
            'id_sector' => ['sometimes', 'required', 'integer', 'exists:sectores,id_sector'],
            'id_institucion_padre' => $hasInstitucionPadre ? ['sometimes', 'nullable', 'integer', 'exists:instituciones_padre,id_institucion_padre'] : ['sometimes', 'nullable'],
            'logo' => ['sometimes', 'nullable'],
            'direccion' => ['sometimes', 'nullable', 'string', 'max:300'],
            'ruc' => ['sometimes', 'nullable', 'string', 'max:20', 'unique:instituciones,ruc,'.$institucion->getKey().',id_institucion'],
        ]);

        if (array_key_exists('nombre', $data)) {
            $data['nombre'] = trim((string) $data['nombre']);
        }
        if (array_key_exists('descripcion', $data) && is_string($data['descripcion'])) {
            $data['descripcion'] = trim($data['descripcion']);
            if ($data['descripcion'] === '') {
                $data['descripcion'] = null;
            }
        }

        if ($request->hasFile('logo')) {
            $request->validate([
                'logo' => ['file', 'max:3072', 'mimes:png,jpg,jpeg,webp'],
            ]);

            $file = $request->file('logo');
            $ext = strtolower((string) ($file->getClientOriginalExtension() ?: ''));
            if ($ext === '') {
                $ext = 'bin';
            }
            $name = Str::uuid().'.'.$ext;
            $absoluteDir = public_path('uploads/logos');
            if (! is_dir($absoluteDir)) {
                mkdir($absoluteDir, 0755, true);
            }
            $file->move($absoluteDir, $name);
            $data['logo'] = 'uploads/logos/'.$name;
        } elseif (array_key_exists('logo', $data) && is_string($data['logo'])) {
            $data['logo'] = trim($data['logo']);
            if ($data['logo'] === '') {
                $data['logo'] = null;
            }
        }

        if (array_key_exists('direccion', $data) && is_string($data['direccion'])) {
            $data['direccion'] = trim($data['direccion']);
            if ($data['direccion'] === '') {
                $data['direccion'] = null;
            }
        }
        if (array_key_exists('ruc', $data) && is_string($data['ruc'])) {
            $data['ruc'] = trim($data['ruc']);
            if ($data['ruc'] === '') {
                $data['ruc'] = null;
            }
        }

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

        $despues = $institucion->loadCount(['representantes', 'documentos'])->toArray();
        HistorialLogger::log(
            $request->user()?->getKey(),
            'institucion_actualizada',
            'instituciones',
            (string) $institucion->getKey(),
            'Actualización de organismo',
            $antes,
            $despues
        );

        $load = ['sector'];
        if ($hasInstitucionPadre) {
            $load[] = 'institucionPadre';
        }

        return response()->json($institucion->load($load)->loadCount(['representantes', 'documentos']));
    }

    public function destroy(Request $request, Institucion $institucion)
    {
        $hasInstitucionPadre = Schema::hasTable('instituciones_padre') && Schema::hasColumn('instituciones', 'id_institucion_padre');

        if ($institucion->estado === 'inactivo') {
            $load = ['sector'];
            if ($hasInstitucionPadre) {
                $load[] = 'institucionPadre';
            }

            return response()->json($institucion->load($load)->loadCount(['representantes', 'documentos']));
        }

        $antes = $institucion->loadCount(['representantes', 'documentos'])->toArray();

        $inUse = DB::table('representantes')->where('id_institucion', $institucion->getKey())->exists()
            || DB::table('documentos_institucion')->where('id_institucion', $institucion->getKey())->exists();

        if ($inUse) {
            $institucion->forceFill(['estado' => 'inactivo'])->save();

            $despues = $institucion->loadCount(['representantes', 'documentos'])->toArray();
            HistorialLogger::log(
                $request->user()?->getKey(),
                'institucion_inactivada',
                'instituciones',
                (string) $institucion->getKey(),
                'Inactivación de organismo (en uso)',
                $antes,
                $despues
            );

            $load = ['sector'];
            if ($hasInstitucionPadre) {
                $load[] = 'institucionPadre';
            }

            return response()->json($institucion->load($load)->loadCount(['representantes', 'documentos']));
        }

        $institucion->forceFill(['estado' => 'inactivo'])->save();
        $despues = $institucion->loadCount(['representantes', 'documentos'])->toArray();

        HistorialLogger::log(
            $request->user()?->getKey(),
            'institucion_inactivada',
            'instituciones',
            (string) $institucion->getKey(),
            'Inactivación de organismo',
            $antes,
            $despues
        );

        $load = ['sector'];
        if ($hasInstitucionPadre) {
            $load[] = 'institucionPadre';
        }

        return response()->json($institucion->load($load)->loadCount(['representantes', 'documentos']));
    }

    public function export(Request $request)
    {
        $query = Institucion::query()
            ->with(['sector:id_sector,nombre_sector'])
            ->withCount(['representantes', 'documentos']);

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

        if ($request->filled('estado')) {
            $query->where('estado', $request->string('estado'));
        }

        if (Schema::hasColumn('instituciones', 'id_institucion_padre') && $request->filled('id_institucion_padre')) {
            $query->where('id_institucion_padre', $request->integer('id_institucion_padre'));
        }

        if ($request->filled('q')) {
            $q = trim((string) $request->string('q'));
            if ($q !== '') {
                $query->where(function ($sub) use ($q) {
                    $sub
                        ->where('nombre', 'like', '%'.$q.'%')
                        ->orWhere('descripcion', 'like', '%'.$q.'%');
                });
            }
        }

        $fileName = 'organismos.xlsx';

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

            $headerStyle = (new Style())->setFontBold();
            $writer->addRow(Row::fromValues([
                'Organismo',
                'Descripción',
                'Sector',
                'Estado',
                'Representantes',
                'Documentos',
            ], $headerStyle));

            $query->orderBy('nombre')->chunk(300, function ($rows) use ($writer) {
                foreach ($rows as $row) {
                    $writer->addRow(Row::fromValues([
                        (string) ($row->nombre ?? ''),
                        (string) ($row->descripcion ?? ''),
                        (string) ($row->sector?->nombre_sector ?? ''),
                        (string) ($row->estado ?? ''),
                        (int) ($row->representantes_count ?? 0),
                        (int) ($row->documentos_count ?? 0),
                    ]));
                }
            });

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

    public function import(Request $request)
    {
        $data = $request->validate([
            'archivo' => ['required', 'file', 'max:5120', 'mimetypes:text/plain,text/csv,application/csv,application/vnd.ms-excel'],
        ]);

        /** @var UploadedFile $file */
        $file = $data['archivo'];
        $path = $file->getRealPath();
        if (! is_string($path) || $path === '') {
            return response()->json(['message' => 'Archivo inválido.'], 422);
        }

        $handle = fopen($path, 'rb');
        if ($handle === false) {
            return response()->json(['message' => 'No se pudo leer el archivo.'], 422);
        }

        $firstLine = fgets($handle);
        if ($firstLine === false) {
            fclose($handle);

            return response()->json(['message' => 'El archivo está vacío.'], 422);
        }

        $delimiter = ',';
        $header = str_getcsv($firstLine, $delimiter);
        if (is_array($header) && count($header) <= 1) {
            $delimiter = ';';
            $header = str_getcsv($firstLine, $delimiter);
        }

        $header = array_map(fn ($h) => strtolower(trim((string) $h)), is_array($header) ? $header : []);
        $idxNombre = array_search('nombre', $header, true);
        $idxDescripcion = array_search('descripcion', $header, true);
        $idxEstado = array_search('estado', $header, true);
        $idxSector = array_search('sector', $header, true);

        if ($idxNombre === false || $idxSector === false) {
            fclose($handle);

            return response()->json(['message' => 'CSV debe incluir las columnas nombre y sector.'], 422);
        }

        $created = 0;
        $updated = 0;
        $errors = [];
        $rowsProcessed = 0;

        $sectoresCache = Sector::query()->pluck('id_sector', 'nombre_sector')->mapWithKeys(function ($id, $nombre) {
            return [strtolower(trim((string) $nombre)) => $id];
        })->toArray();

        DB::beginTransaction();
        try {
            while (($row = fgetcsv($handle, 0, $delimiter)) !== false) {
                $rowsProcessed++;
                if ($rowsProcessed > 2000) {
                    $errors[] = 'Se superó el límite de 2000 filas.';
                    break;
                }

                $nombre = isset($row[$idxNombre]) ? trim((string) $row[$idxNombre]) : '';
                $sectorName = isset($row[$idxSector]) ? trim((string) $row[$idxSector]) : '';

                if ($nombre === '' || $sectorName === '') {
                    continue;
                }

                $sectorNameLower = strtolower($sectorName);
                if (! array_key_exists($sectorNameLower, $sectoresCache)) {
                    $errors[] = "Sector no encontrado: {$sectorName}";

                    continue;
                }
                $sectorId = $sectoresCache[$sectorNameLower];

                $estado = 'activo';
                if ($idxEstado !== false && isset($row[$idxEstado])) {
                    $v = strtolower(trim((string) $row[$idxEstado]));
                    if (in_array($v, ['activo', 'inactivo'], true)) {
                        $estado = $v;
                    }
                }

                $descripcion = null;
                if ($idxDescripcion !== false && isset($row[$idxDescripcion])) {
                    $d = trim((string) $row[$idxDescripcion]);
                    $descripcion = $d === '' ? null : $d;
                }

                $existing = Institucion::query()
                    ->where('nombre', $nombre)
                    ->where('id_sector', $sectorId)
                    ->first();

                if ($existing) {
                    $existing->forceFill([
                        'descripcion' => $descripcion,
                        'estado' => $estado,
                    ])->save();
                    $updated++;
                } else {
                    Institucion::create([
                        'nombre' => $nombre,
                        'descripcion' => $descripcion,
                        'estado' => $estado,
                        'id_sector' => $sectorId,
                    ]);
                    $created++;
                }
            }

            HistorialLogger::log(
                $request->user()?->getKey(),
                'instituciones_importadas',
                'instituciones',
                'bulk',
                'Importación de organismos',
                null,
                ['creados' => $created, 'actualizados' => $updated, 'errores' => array_slice($errors, 0, 10)]
            );

            DB::commit();
        } catch (\Throwable) {
            DB::rollBack();

            return response()->json(['message' => 'No se pudo importar el archivo.'], 500);
        } finally {
            if (is_resource($handle)) {
                fclose($handle);
            }
        }

        return response()->json([
            'ok' => true,
            'creados' => $created,
            'actualizados' => $updated,
            'errores' => array_slice($errors, 0, 20),
        ]);
    }
}
