<?php

namespace Modules\NX600Lab\Console;

use Illuminate\Console\Command;
use Modules\NX600Lab\Entities\NxCommunicationLog;
use Modules\NX600Lab\Entities\NxDeviceConnection;
use Modules\NX600Lab\Entities\NxTestResult;
use Modules\NX600Lab\Entities\NxTestSession;
use Modules\NX600Lab\Entities\Services\Hl7MllpParser;
use Modules\NX600Lab\Entities\Services\VetClinicSyncService;

/**
 * NX600 HL7/MLLP server for Communication Types 4 and C.
 *
 * Listens for HL7 2.5.1 ORU^R01 messages wrapped in MLLP protocol.
 * Run with: php artisan nx600lab:hl7-server
 */
class Nx600Hl7Server extends Command
{
    protected $signature = 'nx600lab:hl7-server
                            {--host= : IP address to listen on}
                            {--port= : HL7 MLLP port}
                            {--type=4 : Communication type (4 or C)}';

    protected $description = 'Start the NX600 HL7/MLLP server for Type 4/C communication';

    private Hl7MllpParser $parser;
    private bool $running = true;

    public function handle(): int
    {
        $this->parser = new Hl7MllpParser();

        $host     = $this->option('host') ?: config('nx600lab.tcp_host', '0.0.0.0');
        $port     = (int) ($this->option('port') ?: config('nx600lab.hl7_port', 2575));
        $commType = $this->option('type') ?: '4';

        $this->info("NX600 Lab — HL7/MLLP Server starting...");
        $this->info("  Host      : {$host}");
        $this->info("  Port      : {$port}");
        $this->info("  Comm Type : {$commType} (HL7 2.5.1)");
        $this->line('');

        $server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if ($server === false) {
            $this->error("Failed to create socket: " . socket_strerror(socket_last_error()));
            return self::FAILURE;
        }

        socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);

        if (!socket_bind($server, $host, $port)) {
            $this->error("Failed to bind {$host}:{$port} - " . socket_strerror(socket_last_error($server)));
            socket_close($server);
            return self::FAILURE;
        }

        socket_listen($server, 5);
        socket_set_nonblock($server);

        $this->info("HL7 server listening on {$host}:{$port}");
        $this->info("Waiting for NX600 device (HL7 Type {$commType}) connection...");
        $this->line("Press Ctrl+C to stop.");
        NxCommunicationLog::info("HL7 server started on {$host}:{$port} (Type {$commType})", 'system');

        $clients     = [];
        $buffers     = [];
        $connections = [];

        while ($this->running) {
            $client = @socket_accept($server);
            if ($client !== false) {
                $peerIp   = '';
                $peerPort = 0;
                socket_getpeername($client, $peerIp, $peerPort);
                $clientId = spl_object_id($client);

                $this->info("[+] HL7 device connected from {$peerIp}:{$peerPort}");
                socket_set_nonblock($client);

                $clients[$clientId] = $client;
                $buffers[$clientId] = '';

                $conn = NxDeviceConnection::create([
                    'device_ip'    => $peerIp,
                    'device_port'  => $peerPort,
                    'comm_type'    => $commType,
                    'status'       => 'connected',
                    'connected_at' => now(),
                ]);
                $connections[$clientId] = $conn->id;

                NxCommunicationLog::info("HL7 device connected from {$peerIp}:{$peerPort}", 'connection', [], $peerIp);
            }

            foreach ($clients as $clientId => $client) {
                $data = @socket_read($client, 8192, PHP_BINARY_READ);

                if ($data === false || $data === '') {
                    $err = socket_last_error($client);
                    if ($err !== 0 && $err !== SOCKET_EAGAIN && $err !== SOCKET_EWOULDBLOCK) {
                        $peerIp = '';
                        @socket_getpeername($client, $peerIp);
                        @socket_close($client);
                        $this->warn("[-] HL7 device disconnected ({$peerIp})");
                        if (isset($connections[$clientId])) {
                            NxDeviceConnection::where('id', $connections[$clientId])->update([
                                'status'          => 'disconnected',
                                'disconnected_at' => now(),
                            ]);
                        }
                        NxCommunicationLog::info("HL7 device disconnected ({$peerIp})", 'connection', [], $peerIp);
                        unset($clients[$clientId], $buffers[$clientId], $connections[$clientId]);
                    }
                    continue;
                }

                $buffers[$clientId] .= $data;

                while (true) {
                    [$message, $buffers[$clientId]] = $this->parser->extractMessage($buffers[$clientId]);
                    if ($message === null) {
                        break;
                    }
                    $this->processHl7Message($message, $client, $commType, $clientId, $connections);
                }
            }

            usleep(10000);
        }

        socket_close($server);
        NxCommunicationLog::info("HL7 server stopped.", 'system');
        $this->info("HL7 server stopped.");
        return self::SUCCESS;
    }

    private function processHl7Message(
        string $message,
        mixed $client,
        string $commType,
        int $clientId,
        array &$connections
    ): void {
        $this->line("  [<] HL7 message received (" . strlen($message) . " bytes)");

        $parsed = $this->parser->parseOru($message);

        // Send ACK back
        $ack = $this->parser->buildAck($parsed['raw_msh'], true);
        @socket_write($client, $ack, strlen($ack));
        $this->line("  [>] ACK sent");

        if (empty($parsed['results'])) {
            $this->warn("  [!] No OBX results found in HL7 message");
            return;
        }

        $patient = $parsed['patient'];
        $order   = $parsed['order'];
        $peerIp  = '';
        @socket_getpeername($client, $peerIp);

        $sampleNo = $order['sample_no'] ?? uniqid('hl7_');

        $session = NxTestSession::updateOrCreate(
            ['sample_no' => $sampleNo, 'test_date' => $order['test_date'] ?? date('Y-m-d')],
            [
                'patient_id'      => $patient['patient_id'] ?? null,
                'patient_name'    => $patient['patient_name'] ?? null,
                'test_condition'  => 'NORMAL',
                'test_date'       => $order['test_date'] ?? date('Y-m-d'),
                'test_time'       => $order['test_time'] ?? date('H:i'),
                'sex'             => $patient['sex'] ?? 9,
                'age'             => 999,
                'species'         => 0,
                'sample_position' => '01',
                'comm_type'       => $commType,
                'device_ip'       => $peerIp,
            ]
        );

        $stored = 0;
        foreach ($parsed['results'] as $t) {
            NxTestResult::updateOrCreate(
                ['test_session_id' => $session->id, 'test_name' => $t['test_name']],
                $t
            );
            $stored++;
        }

        VetClinicSyncService::applyWorklistLinkage($session);

        if (isset($connections[$clientId])) {
            NxDeviceConnection::where('id', $connections[$clientId])->increment('results_stored', $stored);
            NxDeviceConnection::where('id', $connections[$clientId])->increment('messages_received');
        }

        $this->info("  Stored {$stored} HL7 results for sample '{$session->sample_no}'");
        NxCommunicationLog::success(
            "HL7 results stored for '{$session->sample_no}': {$stored} parameters",
            'result',
            ['sample_no' => $session->sample_no, 'count' => $stored, 'session_id' => $session->id],
            $peerIp,
            $session->sample_no
        );
    }
}
