<?php

namespace App\Http\Controllers;

use App\Models\Producto;
use App\Models\ConfigImpresion;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Http;
use Illuminate\Validation\Rule;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Artisan;

class ProductoController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index(Request $request): JsonResponse
    {
        $producto=Producto::leftjoin('marcas', 'productos.marca_id', '=', 'marcas.id')
        ->leftjoin('tipos_servicios', 'productos.tipo_servicio_id', '=', 'tipos_servicios.id')
        ->leftjoin('tipos_productos', 'productos.tipo_producto_id', '=', 'tipos_productos.id')
        ->selectRaw('
            productos.*,
            marcas.marca,
            tipos_servicios.descripcion tipo_tratamiento,
            tipos_productos.descripcion tipo_producto,
            (iva/100) iva_def,
            (ieps/100) ieps_def,
            (productos.precio*(iva/100)) iva,
            productos.precio total
        ')
        ->when($request->filtro,function (Builder $builder) use ($request) {
            $ids=[];
            if(isset($request->filtro)) {
                $ids=array_column($request->filtro,'producto_id');
                foreach ($ids as $key => $value) {
                    if(is_null($value)) {
                        unset($ids[$key]);
                    }
                }
            }
            $builder->whereNotIn('productos.id',$ids);
        })
        ->when($request->search,function (Builder $builder) use ($request) {
            $builder->where(function($query) use ($request) {
                $query->where('productos.id', 'like', "%{$request->search}%")
                    ->orWhere('productos.nombre', 'like', "%{$request->search}%")
                    ->orWhere('productos.codigo_barras', 'like', "%{$request->search}%")
                    ->orWhere('productos.descripcion', 'like', "%{$request->search}%")
                    ->orWhere('marcas.marca', 'like', "%{$request->search}%");
            });
        })
        ->where(['productos.estatus'=>'activo'])->get();
        return response()->json([
            'data' =>$producto
        ], JsonResponse::HTTP_OK);
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request): JsonResponse
    {
        $request->validate(
            [
                'codigo_barras' => ['required', 'max:45',Rule::unique('productos')->ignore('inactivo','estatus')],
                'nombre' => ['required', 'string', 'max:150'],
                'nombre_corto' => ['required', 'string', 'max:30'],
                'precio' => ['required', 'numeric', 'max:999999999.99'],
                'tiene_iva' => ['required', 'max:2'],
                'iva' => ['required', 'numeric', 'max:99'],
                'tiene_ieps' => ['required','max:2'],
                'ieps' => ['required', 'numeric', 'max:99'],
                'estatus' => ['required', 'string','max:10'],
                'marca_id' => ['required', Rule::exists('marcas', 'id')],
                'tipo_producto_id' => ['required', Rule::exists('tipos_productos', 'id')],
                'tipo_servicio_id' => ['required', Rule::exists('tipos_servicios', 'id')],
                'imagen' => 'required|mimes:jpg,png|max:10240',
            ],
            [
                'marca_id.required'=>'El campo marca es obligatorio',
                'marca_id.exists' => 'La marca seleccionada no es válida',
                'tipo_producto_id.required'=>'El campo tipo de producto es obligatorio',
                'marca_id.exists' => 'El tipo de producto seleccionada no es válida',
                'tipo_servicio_id.required'=>'El campo tipo de tratamiento es obligatorio',
                'marca_id.exists' => 'El tipo de tratamiento seleccionada no es válida',
            ]
        );

        $path = $request->imagen->store('public/images/productos');

        $params= $request->only([
            'codigo_barras',
            'nombre',
            'nombre_corto',
            'descripcion',
            'precio',
            'tiene_iva',
            'iva',
            'tiene_ieps',
            'ieps',
            'estatus',
            'marca_id',
            'tipo_producto_id',
            'tipo_servicio_id',
        ]);

        $params['path']=$path;

        $producto = Producto::create($params);

        return response()->json([
            'data' => $producto,
        ], JsonResponse::HTTP_OK);
    }


    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show(Producto $producto): JsonResponse
    {
        return response()->json([
            'data' => [
                'producto'=>$producto,
                'producto_path'=>asset(str_replace("public", "storage", $producto->path)),
                'marca'=>$producto->marca,
                'tipoServicio'=>$producto->tipoServicio,
                'tipoProducto'=>$producto->tipoProducto,
            ],
        ], JsonResponse::HTTP_OK);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, Producto $producto): JsonResponse
    {
        $request->validate(
            [
                'codigo_barras' => ['required', 'max:45',Rule::unique('productos')->where(function ($query) use ($producto) {
                    $query->whereNotIn('id', [$producto->id])->whereNotIn('estatus', ['inactivo']);
                    return $query;
                })],
                'nombre' => ['required', 'string', 'max:150'],
                'nombre_corto' => ['required', 'string', 'max:30'],
                'precio' => ['required', 'numeric', 'max:999999999.99'],
                'tiene_iva' => ['required', 'max:2'],
                'iva' => ['required', 'numeric', 'max:99'],
                'tiene_ieps' => ['required','max:2'],
                'ieps' => ['required', 'numeric', 'max:99'],
                'estatus' => ['required', 'string','max:10'],
                'marca_id' => ['required', Rule::exists('marcas', 'id')],
                'tipo_producto_id' => ['required', Rule::exists('tipos_productos', 'id')],
                'tipo_servicio_id' => ['required', Rule::exists('tipos_servicios', 'id')],
                'imagen' => 'nullable|mimes:jpg,jpeg,png|max:10240',
            ],
            [
                'marca_id.required'=>'El campo marca es obligatorio',
                'marca_id.exists' => 'La marca seleccionada no es válida',
                'tipo_producto_id.required'=>'El campo tipo de producto es obligatorio',
                'marca_id.exists' => 'El tipo de producto seleccionada no es válida',
                'tipo_servicio_id.required'=>'El campo tipo de tratamiento es obligatorio',
                'marca_id.exists' => 'El tipo de tratamiento seleccionada no es válida',
            ]
        );


        $params= $request->only([
            'codigo_barras',
            'nombre',
            'nombre_corto',
            'descripcion',
            'precio',
            'tiene_iva',
            'iva',
            'tiene_ieps',
            'ieps',
            'estatus',
            'marca_id',
            'tipo_producto_id',
            'tipo_servicio_id',
        ]);

        if(!empty($request->imagen)){
            $path = $request->imagen->store('public/images/productos');
            $params['path']=$path;
            if(Storage::exists($producto->path)) {
                unlink(storage_path("app/".$producto->path));
            }
        }


        $producto->update($params);

        return response()->json([
            'data' => $producto,
        ], JsonResponse::HTTP_OK);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy(Producto $producto): JsonResponse
    {
        if (
            \count($producto->tiendas) > 0 ||
            \count($producto->inventarios) > 0 ||
            \count($producto->ingresosMercancias) > 0 ||
            \count($producto->productosBajas) > 0 ||
            \count($producto->ventasDetalles) > 0
        ) {
            $producto->update([
                'estatus' => 'inactivo',
            ]);
        } else {
            if(Storage::exists($producto->path)) {
                unlink(storage_path("app/".$producto->path));
            }
            $producto->delete();
        }
        return response()->json(null, JsonResponse::HTTP_NO_CONTENT);
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function paginacion(Request $request): JsonResponse
    {
        $producto=Producto::leftjoin('marcas', 'productos.marca_id', '=', 'marcas.id')
        ->leftjoin('tipos_servicios', 'productos.tipo_servicio_id', '=', 'tipos_servicios.id')
        ->leftjoin('tipos_productos', 'productos.tipo_producto_id', '=', 'tipos_productos.id')
        ->selectRaw('
            productos.*,
            marcas.marca,
            tipos_servicios.descripcion tipo_tratamiento,
            tipos_productos.descripcion tipo_producto,
            productos.precio total
        ')
        ->when($request->search,function (Builder $builder) use ($request) {
            $builder->orWhere('productos.id', 'like', "%{$request->search}%")
                ->orWhere('productos.nombre', 'like', "%{$request->search}%")
                ->orWhere('productos.descripcion', 'like', "%{$request->search}%")
                ->orWhere('productos.precio', 'like', "%{$request->search}%")
                ->orWhere('marcas.marca', 'like', "%{$request->search}%");
        })
        ->when($request->tipo,function (Builder $builder) use ($request) {
            $builder->Where('productos.tipo_producto_id', '=', "{$request->tipo}");
        })
        ->when($request->tratamiento,function (Builder $builder) use ($request) {
            $builder->Where('productos.tipo_servicio_id', '=', "{$request->tratamiento}");
        })
        ->when($request->marca,function (Builder $builder) use ($request) {
            $builder->Where('productos.marca_id', '=', "{$request->marca}");
        })
        ->where(['productos.estatus'=>'activo'])
        ->orderBy('productos.nombre', 'asc')
        ->paginate($request->perPage);
        // ->orderBy($request->order['name'],$request->order['order'])->paginate($request->perPage);
        return response()->json([
            'data' =>$producto,
        ], JsonResponse::HTTP_OK);
    }

    /**
     * Imprimir código de barras de un producto
     */
    public function imprimirCodigoBarras(Request $request, Producto $producto): JsonResponse
    {
        $request->validate([
            'codigo_barras' => 'required|string|max:45'
        ]);

        // Buscar configuración de impresión activa para etiquetas
        $configuracion = ConfigImpresion::getConfigActiva('etiquetas', null);

        if (!$configuracion) {
            return response()->json([
                'success' => false,
                'message' => 'No se encontró una configuración de impresión activa para etiquetas',
                'fallback' => true
            ], 400);
        }

        // Validar que tenga los campos necesarios
        if (!$configuracion->es_impresion_directa) {
            return response()->json([
                'success' => false,
                'message' => 'Esta configuración no está configurada para impresión directa',
                'fallback' => true
            ], 400);
        }

        if (!$configuracion->ngrok_key) {
            return response()->json([
                'success' => false,
                'message' => 'La configuración no tiene una key de ngrok configurada',
                'fallback' => true
            ], 400);
        }

        try {
            // Obtener los túneles de ngrok con reintentos
            $publicUrl = $this->obtenerTunelNgrokConReintentos($configuracion->ngrok_key);
            
            if (!$publicUrl) {
                return response()->json([
                    'success' => false,
                    'message' => 'No se pudo obtener un túnel activo de ngrok después de varios intentos',
                    'fallback' => true
                ], 400);
            }

            // Construir la URL del endpoint de impresión de código de barras
            $endpointUrl = rtrim($publicUrl, '/') . '/api/imprimir-prueba-tspl';

            // Preparar los datos para enviar al endpoint
            $datosEnvio = [
                'nombreImpresora' => $configuracion->ip_impresora,
                'codigoBarras' => $request->codigo_barras
            ];

            // Intentar hacer una petición al endpoint con reintentos
            $testResponse = $this->enviarPeticionConReintentos($endpointUrl, $datosEnvio);

            if ($testResponse['success']) {
                return response()->json([
                    'success' => true,
                    'message' => 'Se pudo enviar la impresión correctamente'
                ]);
            } else {
                return response()->json([
                    'success' => false,
                    'message' => $testResponse['message'] ?? 'No se pudo acceder al dominio de la impresora ' . $endpointUrl,
                    'error' => $testResponse['error'] ?? null,
                    'endpoint_url' => $endpointUrl,
                    'status_code' => $testResponse['status_code'] ?? 400,
                    'fallback' => true
                ], 400);
            }

        } catch (\Illuminate\Http\Client\ConnectionException $e) {
            return response()->json([
                'success' => false,
                'message' => 'No se pudo conectar al servicio de ngrok o al dominio de la impresora',
                'error' => $e->getMessage(),
                'fallback' => true
            ], 503);
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Error al enviar la impresión del código de barras',
                'error' => $e->getMessage(),
                'fallback' => true
            ], 500);
        }
    }

    /**
     * Obtener túnel de ngrok con reintentos (máximo 3 intentos: 1 inicial + 2 reintentos)
     */
    private function obtenerTunelNgrokConReintentos($ngrokKey, $maxIntentos = 3): ?string
    {
        $ultimoError = null;
        
        for ($intento = 1; $intento <= $maxIntentos; $intento++) {
            try {
                // Esperar un poco antes de reintentar (excepto en el primer intento)
                if ($intento > 1) {
                    sleep(2); // Esperar 2 segundos entre reintentos
                }

                $ngrokResponse = Http::timeout(60)
                    ->withHeaders([
                        'Authorization' => 'Bearer ' . $ngrokKey,
                        'ngrok-version' => '2'
                    ])
                    ->get('https://api.ngrok.com/tunnels');

                if ($ngrokResponse->successful()) {
                    $ngrokData = $ngrokResponse->json();

                    // Verificar que haya túneles disponibles
                    if (isset($ngrokData['tunnels']) && !empty($ngrokData['tunnels'])) {
                        $tunnel = $ngrokData['tunnels'][0];
                        $publicUrl = $tunnel['public_url'] ?? null;

                        if ($publicUrl) {
                            return $publicUrl;
                        }
                    }
                } else {
                    $ultimoError = [
                        'message' => 'No se pudo conectar con la API de ngrok',
                        'error' => $ngrokResponse->json() ?? $ngrokResponse->body(),
                        'status_code' => $ngrokResponse->status()
                    ];
                }
            } catch (\Illuminate\Http\Client\ConnectionException $e) {
                $ultimoError = [
                    'message' => 'Error de conexión con ngrok',
                    'error' => $e->getMessage()
                ];
            } catch (\Exception $e) {
                $ultimoError = [
                    'message' => 'Error inesperado al obtener túnel de ngrok',
                    'error' => $e->getMessage()
                ];
            }
        }

        // Si llegamos aquí, todos los intentos fallaron
        return null;
    }

    /**
     * Enviar petición HTTP con reintentos (máximo 3 intentos: 1 inicial + 2 reintentos)
     */
    private function enviarPeticionConReintentos($endpointUrl, $datosEnvio, $maxIntentos = 3): array
    {
        $ultimoError = null;
        
        for ($intento = 1; $intento <= $maxIntentos; $intento++) {
            try {
                // Esperar un poco antes de reintentar (excepto en el primer intento)
                if ($intento > 1) {
                    sleep(2); // Esperar 2 segundos entre reintentos
                }

                $testResponse = Http::timeout(30)
                    ->withHeaders([
                        'Accept' => 'application/json',
                        'Content-Type' => 'application/json'
                    ])
                    ->post($endpointUrl, $datosEnvio);

                if ($testResponse->successful()) {
                    return [
                        'success' => true,
                        'data' => $testResponse->json()
                    ];
                } else {
                    $ultimoError = [
                        'success' => false,
                        'message' => 'No se pudo acceder al dominio de la impresora ' . $endpointUrl,
                        'error' => $testResponse->json() ?? $testResponse->body(),
                        'status_code' => $testResponse->status()
                    ];
                }
            } catch (\Illuminate\Http\Client\ConnectionException $e) {
                $ultimoError = [
                    'success' => false,
                    'message' => 'No se pudo conectar al dominio de la impresora',
                    'error' => $e->getMessage(),
                    'status_code' => 503
                ];
            } catch (\Exception $e) {
                $ultimoError = [
                    'success' => false,
                    'message' => 'Error inesperado al enviar petición',
                    'error' => $e->getMessage(),
                    'status_code' => 500
                ];
            }
        }

        // Si llegamos aquí, todos los intentos fallaron
        return $ultimoError ?? [
            'success' => false,
            'message' => 'Error desconocido al enviar petición',
            'status_code' => 500
        ];
    }

    /**
     * Generar código de barras EAN-13 único
     * 
     * @return \Illuminate\Http\Response
     */
    public function generarCodigoBarrasEAN13(): JsonResponse
    {
        $maxIntentos = 100;
        $intento = 0;

        while ($intento < $maxIntentos) {
            // Generar código EAN-13
            // Prefijo: 200-299 para uso interno (no comercial)
            $prefijo = '200'; // Prefijo para uso interno (3 dígitos)
            // Generar 9 dígitos adicionales para completar los 12 necesarios antes del checksum
            $codigoProducto = str_pad(rand(0, 999999999), 9, '0', STR_PAD_LEFT);
            $codigoSinChecksum = $prefijo . $codigoProducto;
            
            // Validar que tenga exactamente 12 dígitos
            if (strlen($codigoSinChecksum) !== 12) {
                $intento++;
                continue;
            }
            
            // Calcular dígito de control (checksum)
            $checksum = $this->calcularChecksumEAN13($codigoSinChecksum);
            $codigoBarras = $codigoSinChecksum . $checksum;

            // Verificar si ya existe un producto activo con este código de barras
            // (excluimos inactivos porque según las validaciones, los inactivos pueden reutilizar códigos)
            $existe = Producto::where('codigo_barras', $codigoBarras)
                ->where('estatus', '!=', 'inactivo')
                ->exists();

            // Si no existe, retornar el código generado
            if (!$existe) {
                return response()->json([
                    'success' => true,
                    'codigo_barras' => $codigoBarras
                ], JsonResponse::HTTP_OK);
            }

            // Si existe, continuar con el siguiente intento

            $intento++;
        }

        return response()->json([
            'success' => false,
            'message' => 'No se pudo generar un código de barras único después de varios intentos'
        ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR);
    }

    /**
     * Verificar si un código de barras existe
     * 
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function verificarCodigoBarras(Request $request): JsonResponse
    {
        $request->validate([
            'codigo_barras' => 'required|string|max:45'
        ]);

        $existe = Producto::where('codigo_barras', $request->codigo_barras)
            ->where('estatus', '!=', 'inactivo')
            ->exists();

        return response()->json([
            'existe' => $existe
        ], JsonResponse::HTTP_OK);
    }

    /**
     * Calcular dígito de control (checksum) para EAN-13
     * 
     * @param string $codigo Código de 12 dígitos sin checksum
     * @return string Dígito de control
     */
    private function calcularChecksumEAN13(string $codigo): string
    {
        if (strlen($codigo) !== 12) {
            throw new \InvalidArgumentException('El código debe tener 12 dígitos');
        }

        $suma = 0;
        for ($i = 0; $i < 12; $i++) {
            $digito = (int)$codigo[$i];
            // Multiplicar por 1 o 3 alternativamente
            $multiplicador = ($i % 2 === 0) ? 1 : 3;
            $suma += $digito * $multiplicador;
        }

        $resto = $suma % 10;
        $checksum = ($resto === 0) ? 0 : (10 - $resto);

        return (string)$checksum;
    }
}
