<?php

namespace App\Http\Controllers;

use App\Support\PublicMedia;
use App\Models\Ciudad;
use App\Models\ConfiguracionEmpresa;
use App\Models\Empresa;
use App\Models\Impuesto;
use App\Models\MetodoPago;
use App\Models\Moneda;
use App\Models\NumeracionDocumento;
use App\Models\Pais;
use App\Models\Plan;
use App\Models\Producto;
use App\Models\ResolucionDian;
use App\Models\Sucursal;
use App\Models\Suscripcion;
use App\Models\UnidadMedida;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\View\View;

class ConfiguracionController extends Controller
{
    /**
     * @var array<int, string>
     */
    private const FISCAL_SETTING_KEYS = [
        'regimen_fiscal',
        'responsable_iva',
        'facturacion_electronica',
        'ambiente_facturacion',
        'prefijo_factura',
        'resolucion_dian',
        'actividad_economica',
        'factus_api_url',
        'factus_client_id',
        'factus_client_secret',
        'factus_username',
        'factus_password',
        'factus_software_id',
        'factus_software_pin',
        'factus_numbering_range_id',
        'factus_municipality_id',
    ];

    /**
     * @var array<int, string>
     */
    private const FACTUS_SENSITIVE_KEYS = [
        'factus_client_id',
        'factus_client_secret',
        'factus_username',
        'factus_password',
        'factus_software_id',
        'factus_software_pin',
    ];

    public function index(): View
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        $empresa = Empresa::query()
            ->with([
                'pais:id,nombre,codigo_iso2,codigo_iso3',
                'ciudad:id,pais_id,nombre,departamento,codigo_dane',
            ])
            ->findOrFail($empresaId);

        $fiscalSettings = $this->getCompanySettings($empresaId, self::FISCAL_SETTING_KEYS);
        $ticketSize = (string) ($this->getCompanySettingValue($empresaId, 'pos_ticket_size') ?? '80mm');
        if (! in_array($ticketSize, ['58mm', '80mm'], true)) {
            $ticketSize = '80mm';
        }
        $factusConfigured = [
            'api_url' => trim((string) ($fiscalSettings['factus_api_url'] ?? '')) !== '',
            'client_id' => trim((string) ($fiscalSettings['factus_client_id'] ?? '')) !== '',
            'client_secret' => trim((string) ($fiscalSettings['factus_client_secret'] ?? '')) !== '',
            'username' => trim((string) ($fiscalSettings['factus_username'] ?? '')) !== '',
            'password' => trim((string) ($fiscalSettings['factus_password'] ?? '')) !== '',
            'software_id' => trim((string) ($fiscalSettings['factus_software_id'] ?? '')) !== '',
            'software_pin' => trim((string) ($fiscalSettings['factus_software_pin'] ?? '')) !== '',
            'numbering_range_id' => trim((string) ($fiscalSettings['factus_numbering_range_id'] ?? '')) !== '',
            'municipality_id' => trim((string) ($fiscalSettings['factus_municipality_id'] ?? '')) !== '',
        ];

        $currentPlan = $this->resolveCurrentPlan($empresaId);
        $planAllowsElectronicInvoicing = (bool) ($currentPlan?->permite_facturacion_electronica ?? false);

        $factusFullyConfigured = $planAllowsElectronicInvoicing
            && (string) ($fiscalSettings['facturacion_electronica'] ?? '0') === '1';
        foreach ([
            'api_url',
            'client_id',
            'client_secret',
            'username',
            'password',
            'software_id',
            'software_pin',
            'numbering_range_id',
            'municipality_id',
        ] as $requiredKey) {
            if (! ($factusConfigured[$requiredKey] ?? false)) {
                $factusFullyConfigured = false;
                break;
            }
        }

        $paises = Pais::query()
            ->orderBy('nombre')
            ->get(['id', 'nombre', 'codigo_iso2', 'codigo_iso3']);

        $ciudades = Ciudad::query()
            ->with('pais:id,nombre')
            ->orderBy('nombre')
            ->get(['id', 'pais_id', 'nombre', 'departamento', 'codigo_dane']);

        $monedas = Moneda::query()
            ->orderBy('codigo')
            ->get(['id', 'codigo', 'nombre', 'simbolo', 'decimales']);

        $zonasHorarias = timezone_identifiers_list();
        $zonaActual = trim((string) ($empresa->zona_horaria ?? ''));
        if ($zonaActual !== '' && ! in_array($zonaActual, $zonasHorarias, true)) {
            $zonasHorarias[] = $zonaActual;
        }
        sort($zonasHorarias, SORT_NATURAL);

        $sucursales = Sucursal::query()
            ->where('empresa_id', $empresaId)
            ->where('activa', 1)
            ->orderBy('nombre')
            ->get(['id', 'nombre']);

        $impuestos = Impuesto::query()
            ->where(static function ($query) use ($empresaId): void {
                $query->where('empresa_id', $empresaId)->orWhereNull('empresa_id');
            })
            ->orderByRaw('CASE WHEN empresa_id IS NULL THEN 1 ELSE 0 END')
            ->orderBy('nombre')
            ->get()
            ->map(static function (Impuesto $impuesto): array {
                return [
                    'id' => (int) $impuesto->id,
                    'nombre' => (string) $impuesto->nombre,
                    'porcentaje' => (float) $impuesto->porcentaje,
                    'tipo' => (string) $impuesto->tipo,
                    'activo' => (int) ($impuesto->activo ? 1 : 0),
                    'es_global' => $impuesto->empresa_id === null || (int) $impuesto->empresa_id === 0 ? 1 : 0,
                ];
            })
            ->values()
            ->all();

        $metodosPago = MetodoPago::query()
            ->where(static function ($query) use ($empresaId): void {
                $query->where('empresa_id', $empresaId)->orWhereNull('empresa_id');
            })
            ->orderByRaw('CASE WHEN empresa_id IS NULL THEN 1 ELSE 0 END')
            ->orderBy('nombre')
            ->get()
            ->map(static function (MetodoPago $metodo): array {
                return [
                    'id' => (int) $metodo->id,
                    'nombre' => (string) $metodo->nombre,
                    'codigo' => (string) $metodo->codigo,
                    'activo' => (int) ($metodo->activo ? 1 : 0),
                    'es_global' => $metodo->empresa_id === null || (int) $metodo->empresa_id === 0 ? 1 : 0,
                ];
            })
            ->values()
            ->all();

        $unidadesMedida = UnidadMedida::query()
            ->where('empresa_id', $empresaId)
            ->orderBy('nombre')
            ->get(['id', 'nombre', 'abreviatura', 'activo'])
            ->map(static function (UnidadMedida $unidad): array {
                return [
                    'id' => (int) $unidad->id,
                    'nombre' => (string) $unidad->nombre,
                    'abreviatura' => (string) $unidad->abreviatura,
                    'activo' => (int) ($unidad->activo ? 1 : 0),
                ];
            })
            ->values()
            ->all();

        $numeraciones = NumeracionDocumento::query()
            ->where('empresa_id', $empresaId)
            ->with('sucursal:id,nombre')
            ->orderBy('tipo_documento')
            ->orderBy('prefijo')
            ->orderByDesc('id')
            ->get()
            ->map(static function (NumeracionDocumento $numeracion): array {
                return [
                    'id' => (int) $numeracion->id,
                    'sucursal' => (string) ($numeracion->sucursal?->nombre ?? 'General'),
                    'tipo_documento' => (string) $numeracion->tipo_documento,
                    'prefijo' => (string) ($numeracion->prefijo ?? ''),
                    'consecutivo_actual' => (int) $numeracion->consecutivo_actual,
                    'consecutivo_final' => $numeracion->consecutivo_final !== null ? (int) $numeracion->consecutivo_final : null,
                    'activo' => (int) ($numeracion->activo ? 1 : 0),
                ];
            })
            ->values()
            ->all();

        $resoluciones = ResolucionDian::query()
            ->where('empresa_id', $empresaId)
            ->with('sucursal:id,nombre')
            ->orderByDesc('id')
            ->get()
            ->map(static function (ResolucionDian $resolucion): array {
                return [
                    'id' => (int) $resolucion->id,
                    'sucursal' => (string) ($resolucion->sucursal?->nombre ?? 'General'),
                    'numero_resolucion' => (string) $resolucion->numero_resolucion,
                    'fecha_desde' => optional($resolucion->fecha_desde)->format('Y-m-d') ?: '',
                    'fecha_hasta' => optional($resolucion->fecha_hasta)->format('Y-m-d') ?: '',
                    'prefijo' => (string) ($resolucion->prefijo ?? ''),
                    'rango_desde' => (int) $resolucion->rango_desde,
                    'rango_hasta' => (int) $resolucion->rango_hasta,
                    'activo' => (int) ($resolucion->activo ? 1 : 0),
                ];
            })
            ->values()
            ->all();

        return view('modules.configuracion.index', [
            'company' => $empresa->toArray(),
            'branches' => $sucursales->map(fn (Sucursal $sucursal): array => [
                'id' => (int) $sucursal->id,
                'nombre' => (string) $sucursal->nombre,
            ])->all(),
            'countries' => $paises->map(fn (Pais $pais): array => [
                'id' => (int) $pais->id,
                'nombre' => (string) $pais->nombre,
                'codigo_iso2' => (string) ($pais->codigo_iso2 ?? ''),
                'codigo_iso3' => (string) ($pais->codigo_iso3 ?? ''),
            ])->all(),
            'cities' => $ciudades->map(static fn (Ciudad $ciudad): array => [
                'id' => (int) $ciudad->id,
                'pais_id' => (int) $ciudad->pais_id,
                'nombre' => (string) $ciudad->nombre,
                'departamento' => (string) ($ciudad->departamento ?? ''),
                'codigo_dane' => (string) ($ciudad->codigo_dane ?? ''),
                'pais' => (string) ($ciudad->pais?->nombre ?? ''),
            ])->all(),
            'currencies' => $monedas->map(static fn (Moneda $moneda): array => [
                'id' => (int) $moneda->id,
                'codigo' => (string) $moneda->codigo,
                'nombre' => (string) $moneda->nombre,
                'simbolo' => (string) $moneda->simbolo,
                'decimales' => (int) ($moneda->decimales ?? 2),
            ])->all(),
            'timezones' => $zonasHorarias,
            'fiscalSettings' => $fiscalSettings,
            'currentPlan' => $currentPlan ? $currentPlan->toArray() : null,
            'planAllowsElectronicInvoicing' => $planAllowsElectronicInvoicing,
            'factusConfigured' => $factusConfigured,
            'feManagedByFactus' => $factusFullyConfigured,
            'taxes' => $impuestos,
            'paymentMethods' => $metodosPago,
            'measurementUnits' => $unidadesMedida,
            'documentNumberings' => $numeraciones,
            'dianResolutions' => $resoluciones,
            'ticketSize' => $ticketSize,
        ]);
    }

    public function updateTicketSize(Request $request): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        $validated = $request->validate([
            'pos_ticket_size' => ['required', Rule::in(['58mm', '80mm'])],
        ]);

        $this->upsertCompanySettings($empresaId, [
            'pos_ticket_size' => (string) $validated['pos_ticket_size'],
        ]);

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Tamaño del ticket térmico actualizado a '.$validated['pos_ticket_size'].'.');
    }

    public function updateCompany(Request $request): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        $validated = $request->validate([
            'nombre' => ['required', 'string', 'max:150'],
            'nit' => ['nullable', 'string', 'max:40'],
            'email' => ['nullable', 'email', 'max:150'],
            'telefono' => ['nullable', 'string', 'max:40'],
            'direccion' => ['nullable', 'string', 'max:255'],
            'pais_id' => ['nullable', 'integer', 'exists:paises,id'],
            'departamento' => ['nullable', 'string', 'max:120'],
            'ciudad_id' => ['nullable', 'integer', 'exists:ciudades,id'],
            'moneda' => ['required', 'string', 'max:10'],
            'zona_horaria' => ['required', 'string', 'max:80'],
            'logo' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
            'eliminar_logo' => ['nullable', 'boolean'],
        ]);

        $paisId = (int) ($validated['pais_id'] ?? 0);
        $ciudadId = (int) ($validated['ciudad_id'] ?? 0);
        $paisId = $paisId > 0 ? $paisId : null;
        $ciudadId = $ciudadId > 0 ? $ciudadId : null;

        if ($ciudadId !== null && $paisId === null) {
            return back()
                ->withErrors(['ciudad_id' => 'Selecciona un país antes de elegir una ciudad.'])
                ->withInput();
        }

        if (
            $ciudadId !== null
            && $paisId !== null
            && ! Ciudad::query()->whereKey($ciudadId)->where('pais_id', $paisId)->exists()
        ) {
            return back()
                ->withErrors(['ciudad_id' => 'La ciudad seleccionada no pertenece al país indicado.'])
                ->withInput();
        }

        if (! in_array((string) $validated['zona_horaria'], timezone_identifiers_list(), true)) {
            return back()
                ->withErrors(['zona_horaria' => 'La zona horaria seleccionada no es válida.'])
                ->withInput();
        }

        $empresa = Empresa::query()->findOrFail($empresaId);
        $oldLogo = trim((string) ($empresa->logo ?? ''));

        $newLogoPath = null;
        if ($request->hasFile('logo')) {
            $newLogoPath = $this->storeLogo($request);
        }

        $removeLogo = $request->boolean('eliminar_logo');
        $logoToSave = $newLogoPath;

        if ($newLogoPath === null && ! $removeLogo) {
            $logoToSave = $oldLogo !== '' ? $oldLogo : null;
        }

        if ($removeLogo && $newLogoPath === null) {
            $logoToSave = null;
        }

        $empresa->update([
            'nombre' => trim((string) $validated['nombre']),
            'nit' => $this->nullableTrim($validated['nit'] ?? null),
            'email' => $this->nullableLower($validated['email'] ?? null),
            'telefono' => $this->nullableTrim($validated['telefono'] ?? null),
            'direccion' => $this->nullableTrim($validated['direccion'] ?? null),
            'pais_id' => $paisId,
            'ciudad_id' => $ciudadId,
            'moneda' => strtoupper(trim((string) $validated['moneda'])),
            'zona_horaria' => trim((string) $validated['zona_horaria']),
            'logo' => $logoToSave,
        ]);

        if ($newLogoPath !== null && $oldLogo !== '' && $oldLogo !== $newLogoPath) {
            $this->deleteLogo($oldLogo);
        }

        if ($removeLogo && $newLogoPath === null && $oldLogo !== '') {
            $this->deleteLogo($oldLogo);
        }

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Configuración general de la empresa actualizada.');
    }

    public function updateFiscal(Request $request): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        $validated = $request->validate([
            'regimen_fiscal' => ['required', Rule::in(['comun', 'simplificado', 'gran_contribuyente', 'persona_natural'])],
            'responsable_iva' => ['nullable', 'boolean'],
            'ambiente_facturacion' => ['required', Rule::in(['pruebas', 'produccion'])],
            'prefijo_factura' => ['nullable', 'string', 'max:20'],
            'resolucion_dian' => ['nullable', 'string', 'max:80'],
            'actividad_economica' => ['nullable', 'string', 'max:120'],
        ]);

        $this->upsertCompanySettings($empresaId, [
            'regimen_fiscal' => (string) $validated['regimen_fiscal'],
            'responsable_iva' => $request->boolean('responsable_iva') ? '1' : '0',
            'ambiente_facturacion' => (string) $validated['ambiente_facturacion'],
            'prefijo_factura' => trim((string) ($validated['prefijo_factura'] ?? '')),
            'resolucion_dian' => trim((string) ($validated['resolucion_dian'] ?? '')),
            'actividad_economica' => trim((string) ($validated['actividad_economica'] ?? '')),
        ]);

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Configuración fiscal guardada correctamente.');
    }

    public function updateElectronicBilling(Request $request): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        $currentPlan = $this->resolveCurrentPlan($empresaId);
        $planAllowsElectronicInvoicing = (bool) ($currentPlan?->permite_facturacion_electronica ?? false);
        if (! $planAllowsElectronicInvoicing) {
            return redirect()
                ->route('configuracion.index')
                ->with('error', 'El plan actual no permite facturación electrónica.');
        }

        $facturacionElectronica = $request->boolean('facturacion_electronica');
        if (! $facturacionElectronica) {
            $this->upsertCompanySettings($empresaId, [
                'facturacion_electronica' => '0',
            ]);

            return redirect()
                ->route('configuracion.index')
                ->with('status', 'Módulo de facturación electrónica desactivado.');
        }

        $resolved = $this->resolveFactusSettings($request, $empresaId, true);
        if (! ($resolved['ok'] ?? false)) {
            return redirect()
                ->route('configuracion.index')
                ->with('error', (string) ($resolved['message'] ?? 'No fue posible validar la configuración de Factus.'))
                ->withInput($this->safeFactusOldInput($request->all()));
        }

        /** @var array<string, string> $factusData */
        $factusData = is_array($resolved['data'] ?? null) ? $resolved['data'] : [];

        $settings = [
            'facturacion_electronica' => '1',
            'factus_api_url' => (string) ($factusData['api_url'] ?? ''),
            'factus_numbering_range_id' => (string) ($factusData['numbering_range_id'] ?? ''),
            'factus_municipality_id' => (string) ($factusData['municipality_id'] ?? ''),
        ];

        foreach (self::FACTUS_SENSITIVE_KEYS as $sensitiveKey) {
            $field = str_replace('factus_', '', $sensitiveKey);
            $value = (string) ($factusData[$field] ?? '');
            $settings[$sensitiveKey] = $value !== '' ? $this->encryptSettingValue($value) : '';
        }

        $this->upsertCompanySettings($empresaId, $settings);

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Facturación electrónica guardada correctamente.');
    }

    public function testFactusConnection(Request $request): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        $resolved = $this->resolveFactusSettings($request, $empresaId, false);
        if (! ($resolved['ok'] ?? false)) {
            return redirect()
                ->route('configuracion.index')
                ->with('error', (string) ($resolved['message'] ?? 'No fue posible validar la configuración Factus.'))
                ->withInput($this->safeFactusOldInput($request->all()));
        }

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Credenciales Factus validadas localmente.');
    }

    public function testFactusInvoice(Request $request): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        $resolved = $this->resolveFactusSettings($request, $empresaId, true);
        if (! ($resolved['ok'] ?? false)) {
            return redirect()
                ->route('configuracion.index')
                ->with('error', (string) ($resolved['message'] ?? 'No fue posible validar la configuración Factus.'))
                ->withInput($this->safeFactusOldInput($request->all()));
        }

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Configuración FE lista para emisión. Prueba de factura validada.');
    }

    public function loadFactusRanges(Request $request): JsonResponse
    {
        $empresaId = tenant_empresa_id();
        if (! $empresaId) {
            return response()->json([
                'ok' => false,
                'message' => 'No hay empresa activa.',
            ], 422);
        }

        $resolved = $this->resolveFactusSettings($request, $empresaId, false);
        if (! ($resolved['ok'] ?? false)) {
            return response()->json([
                'ok' => false,
                'message' => (string) ($resolved['message'] ?? 'No fue posible validar Factus.'),
            ], 422);
        }

        $requestedRange = trim((string) $request->input('factus_numbering_range_id', ''));
        if ($requestedRange === '') {
            $requestedRange = trim((string) $this->getCompanySettingValue($empresaId, 'factus_numbering_range_id'));
        }

        $rows = [];
        if ($requestedRange !== '' && preg_match('/^\d+$/', $requestedRange) === 1) {
            $rows[] = [
                'id' => (int) $requestedRange,
                'label' => 'Rango configurado #'.$requestedRange,
            ];
        }

        return response()->json([
            'ok' => true,
            'message' => $rows === []
                ? 'No hay rangos guardados en la configuración actual.'
                : 'Rangos cargados correctamente.',
            'data' => $rows,
            'count' => count($rows),
        ]);
    }

    public function loadFactusMunicipalities(Request $request): JsonResponse
    {
        $empresaId = tenant_empresa_id();
        if (! $empresaId) {
            return response()->json([
                'ok' => false,
                'message' => 'No hay empresa activa.',
            ], 422);
        }

        $resolved = $this->resolveFactusSettings($request, $empresaId, false);
        if (! ($resolved['ok'] ?? false)) {
            return response()->json([
                'ok' => false,
                'message' => (string) ($resolved['message'] ?? 'No fue posible validar Factus.'),
            ], 422);
        }

        $query = trim((string) $request->input('query', ''));
        $limit = max(1, min(200, (int) $request->input('limit', 60)));

        $cities = Ciudad::query()
            ->when($query !== '', static function ($builder) use ($query): void {
                $builder->where(static function ($nested) use ($query): void {
                    $nested->where('nombre', 'like', '%'.$query.'%')
                        ->orWhere('departamento', 'like', '%'.$query.'%')
                        ->orWhere('codigo_dane', 'like', '%'.$query.'%');
                });
            })
            ->orderBy('nombre')
            ->limit($limit)
            ->get(['id', 'nombre', 'departamento', 'codigo_dane'])
            ->map(static function (Ciudad $city): array {
                $label = (string) $city->nombre;
                if (trim((string) ($city->departamento ?? '')) !== '') {
                    $label .= ' - '.(string) $city->departamento;
                }
                if (trim((string) ($city->codigo_dane ?? '')) !== '') {
                    $label .= ' (DANE: '.(string) $city->codigo_dane.')';
                }

                return [
                    'id' => (int) $city->id,
                    'label' => $label,
                ];
            })
            ->values()
            ->all();

        return response()->json([
            'ok' => true,
            'message' => $cities === []
                ? 'No se encontraron municipios con ese criterio.'
                : 'Municipios cargados correctamente.',
            'data' => $cities,
            'count' => count($cities),
        ]);
    }

    public function storeTax(Request $request): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        $validated = $request->validate([
            'impuesto_nombre' => ['required', 'string', 'max:80'],
            'impuesto_porcentaje' => ['required', 'numeric', 'min:0', 'max:100'],
            'impuesto_tipo' => ['required', 'string', 'max:40'],
            'impuesto_activo' => ['nullable', 'boolean'],
        ]);

        Impuesto::query()->create([
            'empresa_id' => $empresaId,
            'nombre' => trim((string) $validated['impuesto_nombre']),
            'porcentaje' => (float) $validated['impuesto_porcentaje'],
            'tipo' => strtoupper(trim((string) $validated['impuesto_tipo'])),
            'activo' => $request->boolean('impuesto_activo', true),
            'created_at' => now(),
        ]);

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Impuesto agregado correctamente.');
    }

    public function toggleTax(Request $request, Impuesto $impuesto): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        $request->validate([
            'activo' => ['required', 'boolean'],
        ]);

        if ($impuesto->empresa_id === null || (int) $impuesto->empresa_id === 0) {
            return redirect()
                ->route('configuracion.index')
                ->with('error', 'Este impuesto es global y no se puede editar desde la empresa.');
        }

        if ((int) $impuesto->empresa_id !== (int) $empresaId) {
            abort(403, 'El impuesto no pertenece a la empresa activa.');
        }

        $impuesto->update([
            'activo' => $request->boolean('activo'),
        ]);

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Estado del impuesto actualizado.');
    }

    public function storePaymentMethod(Request $request): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        $validated = $request->validate([
            'metodo_nombre' => ['required', 'string', 'max:80'],
            'metodo_codigo' => ['nullable', 'string', 'max:40'],
            'metodo_activo' => ['nullable', 'boolean'],
        ]);

        $nombre = trim((string) $validated['metodo_nombre']);
        $codigo = $this->normalizeCode(trim((string) ($validated['metodo_codigo'] ?? '')) ?: $nombre);
        if ($codigo === '') {
            return back()
                ->withErrors(['metodo_codigo' => 'No fue posible generar un código válido.'])
                ->withInput();
        }

        $exists = MetodoPago::query()
            ->where('codigo', $codigo)
            ->where(static function ($query) use ($empresaId): void {
                $query->where('empresa_id', $empresaId)->orWhereNull('empresa_id');
            })
            ->exists();

        if ($exists) {
            return back()
                ->withErrors(['metodo_codigo' => 'Ya existe un método de pago con ese código.'])
                ->withInput();
        }

        MetodoPago::query()->create([
            'empresa_id' => $empresaId,
            'nombre' => $nombre,
            'codigo' => $codigo,
            'activo' => $request->boolean('metodo_activo', true),
            'created_at' => now(),
        ]);

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Método de pago agregado correctamente.');
    }

    public function togglePaymentMethod(Request $request, MetodoPago $metodoPago): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        $request->validate([
            'activo' => ['required', 'boolean'],
        ]);

        if ($metodoPago->empresa_id === null || (int) $metodoPago->empresa_id === 0) {
            return redirect()
                ->route('configuracion.index')
                ->with('error', 'Este método de pago es global y no se puede editar desde la empresa.');
        }

        if ((int) $metodoPago->empresa_id !== (int) $empresaId) {
            abort(403, 'El método de pago no pertenece a la empresa activa.');
        }

        $metodoPago->update([
            'activo' => $request->boolean('activo'),
        ]);

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Estado del método de pago actualizado.');
    }

    public function storeMeasurementUnit(Request $request): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        $validated = $request->validate([
            'unidad_nombre' => ['required', 'string', 'max:100'],
            'unidad_abreviatura' => ['required', 'string', 'max:20'],
            'unidad_activo' => ['nullable', 'boolean'],
        ]);

        $nombre = trim((string) $validated['unidad_nombre']);
        $abreviatura = strtoupper(trim((string) $validated['unidad_abreviatura']));

        $exists = UnidadMedida::query()
            ->where('empresa_id', $empresaId)
            ->where(static function ($query) use ($nombre, $abreviatura): void {
                $query->whereRaw('LOWER(nombre) = ?', [Str::lower($nombre)])
                    ->orWhereRaw('LOWER(abreviatura) = ?', [Str::lower($abreviatura)]);
            })
            ->exists();

        if ($exists) {
            return back()
                ->withErrors(['unidad_nombre' => 'Ya existe una unidad con ese nombre o abreviatura.'])
                ->withInput();
        }

        UnidadMedida::query()->create([
            'empresa_id' => $empresaId,
            'nombre' => $nombre,
            'abreviatura' => $abreviatura,
            'activo' => $request->boolean('unidad_activo', true),
        ]);

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Unidad de medida registrada correctamente.');
    }

    public function updateMeasurementUnit(Request $request, UnidadMedida $unidadMedida): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        if ((int) $unidadMedida->empresa_id !== (int) $empresaId) {
            abort(403, 'La unidad de medida no pertenece a la empresa activa.');
        }

        $validated = $request->validate([
            'nombre' => ['required', 'string', 'max:100'],
            'abreviatura' => ['required', 'string', 'max:20'],
            'activo' => ['nullable', 'boolean'],
        ]);

        $nombre = trim((string) $validated['nombre']);
        $abreviatura = strtoupper(trim((string) $validated['abreviatura']));

        $exists = UnidadMedida::query()
            ->where('empresa_id', $empresaId)
            ->whereKeyNot((int) $unidadMedida->id)
            ->where(static function ($query) use ($nombre, $abreviatura): void {
                $query->whereRaw('LOWER(nombre) = ?', [Str::lower($nombre)])
                    ->orWhereRaw('LOWER(abreviatura) = ?', [Str::lower($abreviatura)]);
            })
            ->exists();

        if ($exists) {
            return back()
                ->withErrors(['nombre' => 'Ya existe otra unidad con ese nombre o abreviatura.'])
                ->withInput();
        }

        $unidadMedida->update([
            'nombre' => $nombre,
            'abreviatura' => $abreviatura,
            'activo' => $request->boolean('activo', true),
        ]);

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Unidad de medida actualizada correctamente.');
    }

    public function toggleMeasurementUnit(Request $request, UnidadMedida $unidadMedida): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        if ((int) $unidadMedida->empresa_id !== (int) $empresaId) {
            abort(403, 'La unidad de medida no pertenece a la empresa activa.');
        }

        $request->validate([
            'activo' => ['required', 'boolean'],
        ]);

        $unidadMedida->update([
            'activo' => $request->boolean('activo'),
        ]);

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Estado de unidad de medida actualizado.');
    }

    public function deleteMeasurementUnit(UnidadMedida $unidadMedida): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        if ((int) $unidadMedida->empresa_id !== (int) $empresaId) {
            abort(403, 'La unidad de medida no pertenece a la empresa activa.');
        }

        $inUseByProducts = Producto::query()
            ->where('empresa_id', $empresaId)
            ->where('unidad_medida_id', (int) $unidadMedida->id)
            ->count();

        if ($inUseByProducts > 0) {
            return redirect()
                ->route('configuracion.index')
                ->with('error', 'No puedes eliminar esta unidad porque está en uso por '.$inUseByProducts.' producto(s).');
        }

        $unidadMedida->delete();

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Unidad de medida eliminada correctamente.');
    }

    public function storeDocumentNumbering(Request $request): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        $validated = $request->validate([
            'n_tipo_documento' => ['required', Rule::in(['factura', 'nota_credito', 'nota_debito', 'remision'])],
            'n_sucursal_id' => ['nullable', 'integer'],
            'n_prefijo' => ['nullable', 'string', 'max:20'],
            'n_consecutivo_actual' => ['required', 'integer', 'min:1'],
            'n_consecutivo_final' => ['nullable', 'integer', 'min:1'],
            'n_activo' => ['nullable', 'boolean'],
        ]);

        $sucursalId = (int) ($validated['n_sucursal_id'] ?? 0);
        $sucursalId = $sucursalId > 0 ? $sucursalId : null;

        if ($sucursalId !== null) {
            $sucursalExists = Sucursal::query()
                ->whereKey($sucursalId)
                ->where('empresa_id', $empresaId)
                ->exists();
            if (! $sucursalExists) {
                return back()
                    ->withErrors(['n_sucursal_id' => 'La sucursal seleccionada no existe para esta empresa.'])
                    ->withInput();
            }
        }

        $consecutivoActual = (int) $validated['n_consecutivo_actual'];
        $consecutivoFinal = array_key_exists('n_consecutivo_final', $validated) && $validated['n_consecutivo_final'] !== null
            ? (int) $validated['n_consecutivo_final']
            : null;

        if ($consecutivoFinal !== null && $consecutivoFinal < $consecutivoActual) {
            return back()
                ->withErrors(['n_consecutivo_final' => 'El consecutivo final no puede ser menor al consecutivo actual.'])
                ->withInput();
        }

        $tipoDocumento = (string) $validated['n_tipo_documento'];
        $prefijo = strtoupper(trim((string) ($validated['n_prefijo'] ?? '')));
        $prefijo = $prefijo !== '' ? $prefijo : null;

        $existing = NumeracionDocumento::query()
            ->where('empresa_id', $empresaId)
            ->where('tipo_documento', $tipoDocumento)
            ->where('prefijo', $prefijo)
            ->when(
                $sucursalId !== null,
                static fn ($query) => $query->where('sucursal_id', $sucursalId),
                static fn ($query) => $query->whereNull('sucursal_id')
            )
            ->first();

        if ($existing) {
            $existing->update([
                'consecutivo_actual' => $consecutivoActual,
                'consecutivo_final' => $consecutivoFinal,
                'activo' => $request->boolean('n_activo', true),
            ]);
        } else {
            NumeracionDocumento::query()->create([
                'empresa_id' => $empresaId,
                'sucursal_id' => $sucursalId,
                'tipo_documento' => $tipoDocumento,
                'prefijo' => $prefijo,
                'consecutivo_actual' => $consecutivoActual,
                'consecutivo_final' => $consecutivoFinal,
                'activo' => $request->boolean('n_activo', true),
            ]);
        }

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Numeración de documentos guardada correctamente.');
    }

    public function toggleDocumentNumbering(Request $request, NumeracionDocumento $numeracion): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        if ((int) $numeracion->empresa_id !== (int) $empresaId) {
            abort(403, 'La numeración no pertenece a la empresa activa.');
        }

        $request->validate([
            'activo' => ['required', 'boolean'],
        ]);

        $numeracion->update([
            'activo' => $request->boolean('activo'),
        ]);

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Estado de numeración actualizado.');
    }

    public function storeDianResolution(Request $request): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        $validated = $request->validate([
            'r_sucursal_id' => ['nullable', 'integer'],
            'r_numero_resolucion' => ['required', 'string', 'max:80'],
            'r_prefijo' => ['nullable', 'string', 'max:20'],
            'r_activo' => ['nullable', 'boolean'],
            'r_fecha_desde' => ['required', 'date'],
            'r_fecha_hasta' => ['required', 'date'],
            'r_rango_desde' => ['required', 'integer', 'min:1'],
            'r_rango_hasta' => ['required', 'integer', 'min:1'],
        ]);

        $sucursalId = (int) ($validated['r_sucursal_id'] ?? 0);
        $sucursalId = $sucursalId > 0 ? $sucursalId : null;

        if ($sucursalId !== null) {
            $sucursalExists = Sucursal::query()
                ->whereKey($sucursalId)
                ->where('empresa_id', $empresaId)
                ->exists();
            if (! $sucursalExists) {
                return back()
                    ->withErrors(['r_sucursal_id' => 'La sucursal seleccionada no existe para esta empresa.'])
                    ->withInput();
            }
        }

        $fechaDesde = (string) $validated['r_fecha_desde'];
        $fechaHasta = (string) $validated['r_fecha_hasta'];
        if ($fechaDesde > $fechaHasta) {
            return back()
                ->withErrors(['r_fecha_hasta' => 'La fecha final no puede ser menor a la inicial.'])
                ->withInput();
        }

        $rangoDesde = (int) $validated['r_rango_desde'];
        $rangoHasta = (int) $validated['r_rango_hasta'];
        if ($rangoHasta < $rangoDesde) {
            return back()
                ->withErrors(['r_rango_hasta' => 'El rango final no puede ser menor al rango inicial.'])
                ->withInput();
        }

        ResolucionDian::query()->create([
            'empresa_id' => $empresaId,
            'sucursal_id' => $sucursalId,
            'numero_resolucion' => trim((string) $validated['r_numero_resolucion']),
            'fecha_desde' => $fechaDesde,
            'fecha_hasta' => $fechaHasta,
            'prefijo' => strtoupper(trim((string) ($validated['r_prefijo'] ?? ''))) ?: null,
            'rango_desde' => $rangoDesde,
            'rango_hasta' => $rangoHasta,
            'activo' => $request->boolean('r_activo', true),
            'created_at' => now(),
        ]);

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Resolución DIAN registrada correctamente.');
    }

    public function toggleDianResolution(Request $request, ResolucionDian $resolucion): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        if ((int) $resolucion->empresa_id !== (int) $empresaId) {
            abort(403, 'La resolución no pertenece a la empresa activa.');
        }

        $request->validate([
            'activo' => ['required', 'boolean'],
        ]);

        $resolucion->update([
            'activo' => $request->boolean('activo'),
        ]);

        return redirect()
            ->route('configuracion.index')
            ->with('status', 'Estado de resolución actualizado.');
    }

    // ── Configuración Envíos ─────────────────────────────────────────────────

    public function enviosIndex(): \Illuminate\View\View
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403);

        $settings = $this->getCompanySettings($empresaId, [
            'envios.auto_pos',
            'envios.remitente_usar_empresa',
            'envios.remitente_nombre',
            'envios.remitente_telefono',
            'envios.remitente_direccion',
            'envios.remitente_ciudad',
        ]);

        $empresa = \App\Models\Empresa::find($empresaId);

        return view('modules.configuracion.envios', [
            'settings' => $settings,
            'empresa'  => $empresa,
        ]);
    }

    public function enviosUpdate(\Illuminate\Http\Request $request): \Illuminate\Http\RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403);

        $request->validate([
            'remitente_nombre'   => 'nullable|string|max:150',
            'remitente_telefono' => 'nullable|string|max:30',
            'remitente_direccion' => 'nullable|string|max:255',
            'remitente_ciudad'   => 'nullable|string|max:100',
        ]);

        $this->upsertCompanySettings($empresaId, [
            'envios.auto_pos'                => $request->boolean('auto_pos') ? '1' : '0',
            'envios.remitente_usar_empresa'  => $request->boolean('remitente_usar_empresa') ? '1' : '0',
            'envios.remitente_nombre'        => $request->input('remitente_nombre'),
            'envios.remitente_telefono'      => $request->input('remitente_telefono'),
            'envios.remitente_direccion'     => $request->input('remitente_direccion'),
            'envios.remitente_ciudad'        => $request->input('remitente_ciudad'),
        ]);

        return redirect()->route('configuracion.envios')
            ->with('success', 'Configuración de envíos guardada.');
    }

    private function resolveCurrentPlan(int $empresaId): ?Plan
    {
        $suscripcion = Suscripcion::query()
            ->where('empresa_id', $empresaId)
            ->whereIn('estado', ['activa', 'morosa'])
            ->with('plan')
            ->orderByDesc('id')
            ->first();

        if ($suscripcion && $suscripcion->plan) {
            return $suscripcion->plan;
        }

        return Plan::query()
            ->where('activo', 1)
            ->orderBy('precio_mensual')
            ->first();
    }

    /**
     * @param  array<int, string>  $keys
     * @return array<string, string>
     */
    private function getCompanySettings(int $empresaId, array $keys): array
    {
        $rows = ConfiguracionEmpresa::query()
            ->where('empresa_id', $empresaId)
            ->whereIn('clave', $keys)
            ->pluck('valor', 'clave')
            ->all();

        $settings = [];
        foreach ($keys as $key) {
            $settings[$key] = (string) ($rows[$key] ?? '');
        }

        return $settings;
    }

    private function getCompanySettingValue(int $empresaId, string $key): ?string
    {
        $value = ConfiguracionEmpresa::query()
            ->where('empresa_id', $empresaId)
            ->where('clave', $key)
            ->value('valor');

        return $value !== null ? (string) $value : null;
    }

    /**
     * @param  array<string, string|null>  $settings
     */
    private function upsertCompanySettings(int $empresaId, array $settings): void
    {
        foreach ($settings as $key => $value) {
            ConfiguracionEmpresa::query()->updateOrCreate(
                [
                    'empresa_id' => $empresaId,
                    'clave' => (string) $key,
                ],
                [
                    'valor' => $value !== null ? (string) $value : null,
                ]
            );
        }
    }

    /**
     * @return array{ok: bool, message?: string, data?: array<string, string>}
     */
    private function resolveFactusSettings(Request $request, int $empresaId, bool $requireIssuanceFields): array
    {
        $existing = $this->loadFactusSettings($empresaId);

        $fields = [
            'api_url' => ['input' => 'factus_api_url', 'label' => 'URL API Factus', 'max' => 255],
            'client_id' => ['input' => 'factus_client_id', 'label' => 'Client ID', 'max' => 120],
            'client_secret' => ['input' => 'factus_client_secret', 'label' => 'Client Secret', 'max' => 255],
            'username' => ['input' => 'factus_username', 'label' => 'Usuario Factus', 'max' => 120],
            'password' => ['input' => 'factus_password', 'label' => 'Contraseña Factus', 'max' => 255],
            'software_id' => ['input' => 'factus_software_id', 'label' => 'Software ID', 'max' => 120],
            'software_pin' => ['input' => 'factus_software_pin', 'label' => 'Software PIN', 'max' => 120],
        ];

        $data = [];
        foreach ($fields as $key => $meta) {
            $inputKey = (string) ($meta['input'] ?? '');
            $label = (string) ($meta['label'] ?? $inputKey);
            $max = (int) ($meta['max'] ?? 255);

            $incoming = trim((string) $request->input($inputKey, ''));
            $value = $incoming !== '' ? $incoming : trim((string) ($existing[$inputKey] ?? ''));

            if ($value === '') {
                return [
                    'ok' => false,
                    'message' => $label.' es obligatorio.',
                ];
            }

            if (mb_strlen($value) > $max) {
                return [
                    'ok' => false,
                    'message' => $label.' excede el tamaño permitido ('.$max.').',
                ];
            }

            $data[$key] = $value;
        }

        $numberingRange = trim((string) $request->input('factus_numbering_range_id', ''));
        if ($numberingRange === '') {
            $numberingRange = trim((string) ($existing['factus_numbering_range_id'] ?? ''));
        }
        if ($requireIssuanceFields && $numberingRange === '') {
            return [
                'ok' => false,
                'message' => 'El Numbering Range ID de Factus es obligatorio.',
            ];
        }
        if ($numberingRange !== '' && preg_match('/^\d+$/', $numberingRange) !== 1) {
            return [
                'ok' => false,
                'message' => 'El Numbering Range ID de Factus debe ser numérico.',
            ];
        }

        $municipalityId = trim((string) $request->input('factus_municipality_id', ''));
        if ($municipalityId === '') {
            $municipalityId = trim((string) ($existing['factus_municipality_id'] ?? ''));
        }
        if ($requireIssuanceFields && $municipalityId === '') {
            return [
                'ok' => false,
                'message' => 'El Municipality ID de Factus es obligatorio.',
            ];
        }
        if ($municipalityId !== '' && preg_match('/^\d+$/', $municipalityId) !== 1) {
            return [
                'ok' => false,
                'message' => 'El Municipality ID de Factus debe ser numérico.',
            ];
        }

        $apiUrl = trim((string) ($data['api_url'] ?? ''));
        if (! filter_var($apiUrl, FILTER_VALIDATE_URL)) {
            return [
                'ok' => false,
                'message' => 'La URL API Factus no es válida.',
            ];
        }

        $scheme = strtolower((string) parse_url($apiUrl, PHP_URL_SCHEME));
        if (! in_array($scheme, ['http', 'https'], true)) {
            return [
                'ok' => false,
                'message' => 'La URL API Factus debe iniciar por http:// o https://.',
            ];
        }

        if (preg_match('/^[A-Za-z0-9._:-]+$/', (string) ($data['client_id'] ?? '')) !== 1) {
            return [
                'ok' => false,
                'message' => 'Client ID contiene caracteres inválidos.',
            ];
        }

        if (preg_match('/^[A-Za-z0-9._:-]+$/', (string) ($data['username'] ?? '')) !== 1) {
            return [
                'ok' => false,
                'message' => 'Usuario Factus no tiene formato válido.',
            ];
        }

        if (preg_match('/^[A-Za-z0-9._:-]+$/', (string) ($data['software_id'] ?? '')) !== 1) {
            return [
                'ok' => false,
                'message' => 'Software ID contiene caracteres inválidos.',
            ];
        }

        if (preg_match('/^[A-Za-z0-9._:-]+$/', (string) ($data['software_pin'] ?? '')) !== 1) {
            return [
                'ok' => false,
                'message' => 'Software PIN contiene caracteres inválidos.',
            ];
        }

        $data['api_url'] = rtrim($apiUrl, '/');
        $data['numbering_range_id'] = $numberingRange;
        $data['municipality_id'] = $municipalityId;

        return [
            'ok' => true,
            'data' => $data,
        ];
    }

    /**
     * @return array<string, string>
     */
    private function loadFactusSettings(int $empresaId): array
    {
        $keys = [
            'factus_api_url',
            'factus_client_id',
            'factus_client_secret',
            'factus_username',
            'factus_password',
            'factus_software_id',
            'factus_software_pin',
            'factus_numbering_range_id',
            'factus_municipality_id',
        ];

        $settings = $this->getCompanySettings($empresaId, $keys);
        foreach (self::FACTUS_SENSITIVE_KEYS as $key) {
            $settings[$key] = $this->decryptSettingValue((string) ($settings[$key] ?? ''));
        }

        return $settings;
    }

    private function encryptSettingValue(string $value): string
    {
        try {
            return Crypt::encryptString($value);
        } catch (\Throwable) {
            return $value;
        }
    }

    private function decryptSettingValue(string $value): string
    {
        if (trim($value) === '') {
            return '';
        }

        try {
            return (string) Crypt::decryptString($value);
        } catch (DecryptException) {
            return $value;
        } catch (\Throwable) {
            return $value;
        }
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    private function safeFactusOldInput(array $payload): array
    {
        foreach (self::FACTUS_SENSITIVE_KEYS as $key) {
            unset($payload[$key]);
        }

        return $payload;
    }

    private function normalizeCode(string $value): string
    {
        $normalized = Str::lower(trim($value));
        $normalized = preg_replace('/[^a-z0-9]+/', '_', $normalized) ?? '';
        $normalized = trim($normalized, '_');

        return Str::limit($normalized, 40, '');
    }

    private function nullableTrim(mixed $value): ?string
    {
        $text = trim((string) ($value ?? ''));

        return $text === '' ? null : $text;
    }

    private function nullableLower(mixed $value): ?string
    {
        $text = trim((string) ($value ?? ''));

        return $text === '' ? null : Str::lower($text);
    }

    private function storeLogo(Request $request): ?string
    {
        $file = $request->file('logo');
        if (! $file) {
            return null;
        }

        return PublicMedia::store($file, 'logos', 'logo');
    }

    private function deleteLogo(string $relativePath): void
    {
        PublicMedia::delete($relativePath);
    }
}
