using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Channels;
using GatewaySCO;
using Serilog;

namespace gatewaySCO.POSBC;

// Maneja conexión al POSBC.
public class ClienteServidorPOSBC(string ip, int pto)
{
    readonly ILogger log = Log.ForContext<ClienteServidorPOSBC>();
    private int _pto = pto;
    private string _ip = ip;
    public  Socket Socket { get;  private set; }
    public bool ConexionActiva { get; private set; } = false;
    private int _nroMsjEnviados = 0;
    private int _nroMsjRecibidos;

    public void AbreConexion()
    {
        Socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        try
        {
            // Conectarse al servidor
            Socket.Connect(new IPEndPoint(IPAddress.Parse(_ip), _pto));
            ConexionActiva = true;
            Log.Information("Conectado a POSBC #1 {ip}:{pto} - {conectado}", _ip, _pto, Socket.Connected);
        }
        catch (SocketException ex)
        {
            ConexionActiva = false;
            Log.Error("Excepción socket conexión a POSBC {ip}:{pto} - código error socket {codigo} - {e}", _ip, ex.SocketErrorCode, _pto, ex);
        }
        catch (Exception e)
        {
            ConexionActiva = false;
            Log.Error("Excepción en conexión a POSBC {ip}:{pto} - {e}", _ip, _pto, e);
        }
    }

    public void CierraConexion()
    {
        try
        {
            // Cerrar el socket
            ConexionActiva = false;
            Socket.Shutdown(SocketShutdown.Both);
            Socket.Close();
        }
        catch (Exception e)
        {
            Log.Warning("Excepción cerrando conexión a POSBC {ip}:{pto} - {e}", _ip, _pto, e);
        }
        Log.Information("Conexión cerrada a POSBC {ip}:{pto} - {conectado}", _ip, _pto, Socket.Connected);
    }

    /// <summary>
    /// Envia mensaje al POSBC
    /// </summary>
    public async Task Envia(string msj)
    {
        var taskId = Task.CurrentId?.ToString() ?? "no-task";
        // Empaca mensaje en bytes.
        byte[] buffer = Util.ConvierteEnBufferBytes(msj);
        await Socket.SendAsync(buffer);
        _nroMsjEnviados++;
        Log.Information("{tid} GTWY -> POSBC {nro} - {bytes} bytes total", taskId, _nroMsjEnviados, buffer.Length);
        Log.Verbose("{tid} GTWY -> POSBC mensaje\n{msj}", taskId, msj);
    }

    // public async Task Recibe(Channel<string> canalSalida)
    // {
    //     var taskId = Task.CurrentId?.ToString() ?? "no-task";
    //     Log.Debug("{tid} Tarea POSBC acepta entradas iniciada", taskId);
    //     while (true)
    //     {
    //         // Lee longitud mensaje entrante, 4 bytes.
    //         Log.Information("{tid} Esperando POSBC", taskId);
    //         var bufferLongitud = new byte[4];

    //         // Lee los primeros 4 bytes de los datos de entrada, los cuales indican 
    //         // la longitud del resto del mensaje.
    //         int nroBytesLeidos = await Socket.ReceiveAsync(new ArraySegment<byte>(bufferLongitud), SocketFlags.None);

    //         // Si el número de bytes leidos es cero, la conexión se ha cerrado.
    //         // El método Receive es sincróno, luego si retorna sin datos, es que no hay conexión.
    //         // Mientras tenga conexión, se queda esperando datos.
    //         if (nroBytesLeidos == 0) break;

    //         int longitudMensaje = Util.LongitudCodificada(bufferLongitud);
    //         Log.Debug("{tid} POSBC bytes cabecera {nroBytes}, longitud mensaje {longitud}", taskId, nroBytesLeidos, longitudMensaje);

    //         // Prepara buffer de entrada según la longitud esperada del mensaje.
    //         var bufferMensaje = new byte[longitudMensaje];
    //         nroBytesLeidos = await Socket.ReceiveAsync(new ArraySegment<byte>(bufferMensaje), SocketFlags.None);
    //         Log.Debug("{tid} POSBC bytes mensaje {nroBytes}", taskId, nroBytesLeidos);
    //         _nroMsjRecibidos++;
    //         Log.Information("{tid} GTWY <- POSBC {nro} - {bytes} bytes cuerpo", taskId, _nroMsjRecibidos, nroBytesLeidos);

    //         // Arreglo de bytes de entrada se transforma en string, 
    //         // constituye el mensaje.
    //         string mensaje = Encoding.UTF8.GetString(bufferMensaje, 0, nroBytesLeidos);
    //         Log.Verbose("{tid} GTWY <- POSBC mensaje\n{msj}", taskId, mensaje);

    //         // Se agrega mensaje a la cola de entrada de mensajes.
    //         await canalSalida.Writer.WriteAsync(mensaje);
    //         Log.Debug("{tid} POSBC mensaje -> [cola salida]", taskId);
    //     }
    // }

    // Envia mensaje, espera respuesta y la retorna como un arreglo de bytes.
    // El mensaje es un string, convertido a arreglo de bytes, y le antepone
    // 4 bytes con la longitud del mensaje.
    // TODO : validar que la respuesta sean mas de 1 mensaje!!!
    public byte[] EnviaRecibe(byte[] bytesMensaje)
    {
        // Remite mensaje.
        Socket.Send(bytesMensaje);
        _nroMsjEnviados++;
        Log.Information("Mensaje #{contador} para POSBIC {ip}", _nroMsjEnviados, _ip);
        Log.Information("Esperando respuesta..");

        // Leer la respuesta del servidor
        byte[] buffer = new byte[1024]; // Tamaño del buffer inicial
        int totalBytesRead = 0;
        int bytesRead;
        byte[] bufferEntrada;

        // Usar un MemoryStream para manejar datos de longitud arbitraria
        using (var ms = new System.IO.MemoryStream(1024))
        {
            while ((bytesRead = Socket.Receive(buffer)) > 0)
            {
                ms.Write(buffer, 0, bytesRead);
                totalBytesRead += bytesRead;

                // Romper el ciclo si se ha leído todo el mensaje
                if (Socket.Available == 0) break;
            }

            // Obtener todos los datos recibidos como un arreglo de bytes
            bufferEntrada = ms.ToArray();
        }
        _nroMsjRecibidos++;

        Log.Information("Respuesta #{contador} de POSBC {ip}", _nroMsjRecibidos, _ip);
        Log.Debug("Mensaje - {msj}", _nroMsjRecibidos, _ip, Encoding.UTF8.GetString(bufferEntrada));
        Log.Debug(Util.DetalleMensajes(bufferEntrada));
        return bufferEntrada;
    }
}