<?php

namespace Modules\LabCore\Entities\Services;

use Illuminate\Support\Facades\DB;
use Modules\LabCore\Entities\LabAgent;
use Modules\LabCore\Entities\LabAnalyteMap;
use Modules\LabCore\Entities\LabDevice;
use Modules\LabCore\Entities\LabTestResult;
use Modules\LabCore\Entities\LabTestSession;

/**
 * Turns one raw device message (relayed by a LabBridge Agent) into a stored
 * LabTestSession + LabTestResult set.
 *
 * Flow:  raw  →  driver->parse()  →  driver->normalize()  →  persist
 *
 * Idempotent on the agent-supplied uuid: a replayed message (after a lost
 * confirmation) is detected and skipped rather than duplicated.
 */
class ResultIngestionService
{
    /**
     * @return array{status:string, session_id?:int, results?:int, reason?:string}
     */
    public function ingest(
        LabAgent $agent,
        string $vendor,
        string $raw,
        string $uuid,
        ?int $deviceId = null,
        array $context = []
    ): array {
        $existing = LabTestSession::where('uuid', $uuid)->first();
        if ($existing) {
            return ['status' => 'duplicate', 'session_id' => $existing->id];
        }

        $driver = DriverRegistry::get($vendor);
        if (!$driver) {
            return ['status' => 'no_driver', 'reason' => "No driver registered for vendor '{$vendor}'"];
        }

        $device  = $deviceId ? LabDevice::find($deviceId) : null;
        $context = array_merge([
            'business_id'   => $agent->business_id,
            'comm_protocol' => $device?->comm_protocol,
            'device_id'     => $deviceId,
        ], $context);

        $parsed = $driver->parse($raw, $context);
        if ($parsed === null) {
            return ['status' => 'not_a_result', 'reason' => 'Message is not a parseable result payload'];
        }

        $normalized  = $driver->normalize($parsed);
        $sessionData = $normalized['session'] ?? [];
        $results     = $normalized['results'] ?? [];

        return DB::transaction(function () use ($agent, $vendor, $uuid, $deviceId, $sessionData, $results) {
            $session = new LabTestSession();
            $session->forceFill(array_merge($sessionData, [
                'uuid'        => $uuid,
                'vendor'      => $vendor,
                'agent_id'    => $agent->id,
                'device_id'   => $deviceId,
                'business_id' => $agent->business_id,
                'source'      => 'agent',
            ]));
            $session->save();

            $species = $sessionData['species'] ?? null;

            foreach ($results as $row) {
                $mapped = LabAnalyteMap::resolve(
                    $vendor,
                    $row['analyte_code'] ?? '',
                    $species,
                    $agent->business_id
                );

                LabTestResult::create(array_merge($row, [
                    'test_session_id' => $session->id,
                    'analyte_name'    => $row['analyte_name'] ?? $mapped?->canonical_name,
                    'unit'            => ($row['unit'] ?? null) ?: $mapped?->unit,
                    'ref_lower'       => ($row['ref_lower'] ?? null) ?: $mapped?->ref_lower,
                    'ref_upper'       => ($row['ref_upper'] ?? null) ?: $mapped?->ref_upper,
                ]));
            }

            return ['status' => 'stored', 'session_id' => $session->id, 'results' => count($results)];
        });
    }
}
