<?php

namespace Modules\LabCore\Http\Controllers\Api;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\LabCore\Entities\LabAgent;
use Modules\LabCore\Entities\LabCommunicationLog;
use Modules\LabCore\Entities\LabDeviceError;
use Modules\LabCore\Entities\LabTestSession;
use Modules\LabCore\Entities\LabWorklist;
use Modules\LabCore\Entities\Services\ResultIngestionService;

/**
 * Cloud-side API consumed by the standalone LabBridge Agent installed at each
 * clinic. The agent authenticates with a per-agent bearer token.
 *
 * Phase 1: agent lifecycle (register/heartbeat), worklist pull and the sync
 * reconcile manifest are functional. Raw device messages are accepted and
 * logged; turning a raw message into stored sessions/results is wired to the
 * vendor drivers in Phase 2.
 */
class AgentController extends Controller
{
    /**
     * Resolve the authenticated agent from the bearer token, or null.
     */
    private function agent(Request $request): ?LabAgent
    {
        $token = $request->bearerToken();
        if (!$token) {
            return null;
        }
        return LabAgent::where('api_token', $token)->where('status', 'active')->first();
    }

    private function unauthorized(): JsonResponse
    {
        return response()->json(['error' => 'invalid_agent_token'], 401);
    }

    /**
     * POST /api/lab/agent/register
     * Agent announces itself; receives its device list.
     */
    public function register(Request $request): JsonResponse
    {
        $agent = $this->agent($request);
        if (!$agent) {
            return $this->unauthorized();
        }

        $agent->update([
            'agent_version'     => $request->input('agent_version'),
            'last_ip'           => $request->ip(),
            'last_heartbeat_at' => now(),
        ]);

        LabCommunicationLog::info("Agent '{$agent->name}' registered", 'sync', [
            'agent_id' => $agent->id,
        ]);

        return response()->json([
            'agent'   => ['id' => $agent->id, 'name' => $agent->name, 'code' => $agent->code],
            'devices' => $agent->devices()->get([
                'id', 'vendor', 'model', 'name', 'connection_type',
                'comm_protocol', 'host', 'port', 'com_port', 'baud',
                'parity', 'data_bits', 'stop_bits', 'status', 'settings',
            ]),
        ]);
    }

    /**
     * POST /api/lab/agent/heartbeat
     * Light periodic ping carrying the agent's queue depth + device states.
     */
    public function heartbeat(Request $request): JsonResponse
    {
        $agent = $this->agent($request);
        if (!$agent) {
            return $this->unauthorized();
        }

        $agent->update([
            'last_ip'           => $request->ip(),
            'last_heartbeat_at' => now(),
            'queue_depth'       => (int) $request->input('queue_depth', 0),
            'agent_version'     => $request->input('agent_version', $agent->agent_version),
        ]);

        return response()->json(['ok' => true, 'server_time' => now()->toIso8601String()]);
    }

    /**
     * POST /api/lab/results
     * Accepts an assembled raw device message, parses it through the vendor
     * driver and stores the session + results. Idempotent on the agent-supplied
     * uuid.
     */
    public function results(Request $request, ResultIngestionService $ingestion): JsonResponse
    {
        $agent = $this->agent($request);
        if (!$agent) {
            return $this->unauthorized();
        }

        $data = $request->validate([
            'uuid'        => 'required|uuid',
            'device_id'   => 'nullable|integer',
            'vendor'      => 'required|string|max:30',
            'raw_message' => 'required|string',
            'received_at' => 'nullable|date',
        ]);

        LabCommunicationLog::info('Raw result message received from agent', 'result', [
            'agent_id'    => $agent->id,
            'device_id'   => $data['device_id'] ?? null,
            'vendor'      => $data['vendor'],
            'direction'   => 'in',
            'raw_message' => $data['raw_message'],
        ]);

        try {
            $outcome = $ingestion->ingest(
                $agent,
                $data['vendor'],
                $data['raw_message'],
                $data['uuid'],
                $data['device_id'] ?? null
            );
        } catch (\Throwable $e) {
            \Log::error('LabCore result ingestion failed: ' . $e->getMessage());
            LabCommunicationLog::error('Result ingestion failed: ' . $e->getMessage(), 'result', [
                'agent_id' => $agent->id,
                'vendor'   => $data['vendor'],
            ]);
            return response()->json(['status' => 'error', 'reason' => $e->getMessage()], 422);
        }

        return response()->json($outcome);
    }

    /**
     * POST /api/lab/events
     * Batched connection / error events accumulated by the agent (incl. while
     * it was offline).
     */
    public function events(Request $request): JsonResponse
    {
        $agent = $this->agent($request);
        if (!$agent) {
            return $this->unauthorized();
        }

        $events = $request->input('events', []);
        $stored = 0;

        foreach ($events as $event) {
            $type = $event['type'] ?? 'log';
            if ($type === 'error') {
                LabDeviceError::create([
                    'agent_id'   => $agent->id,
                    'device_id'  => $event['device_id'] ?? null,
                    'vendor'     => $event['vendor'] ?? null,
                    'error_code' => $event['error_code'] ?? null,
                    'error_date' => $event['error_date'] ?? null,
                    'error_time' => $event['error_time'] ?? null,
                    'message'    => $event['message'] ?? null,
                    'added_info' => $event['added_info'] ?? null,
                    'device_ip'  => $event['device_ip'] ?? null,
                ]);
            } else {
                LabCommunicationLog::write(
                    $event['level'] ?? 'info',
                    $event['message'] ?? '',
                    $event['category'] ?? 'connection',
                    [
                        'agent_id'  => $agent->id,
                        'device_id' => $event['device_id'] ?? null,
                        'vendor'    => $event['vendor'] ?? null,
                        'device_ip' => $event['device_ip'] ?? null,
                        'context'   => $event['context'] ?? null,
                    ]
                );
            }
            $stored++;
        }

        return response()->json(['status' => 'ok', 'stored' => $stored]);
    }

    /**
     * GET /api/lab/worklist
     * Pending orders for the agent's devices, for the agent to cache and serve
     * to bidirectional analyzers.
     */
    public function worklist(Request $request): JsonResponse
    {
        $agent = $this->agent($request);
        if (!$agent) {
            return $this->unauthorized();
        }

        $entries = LabWorklist::where('agent_id', $agent->id)
            ->pending()
            ->orderBy('created_at')
            ->get([
                'id', 'device_id', 'vendor', 'sample_no', 'patient_id',
                'patient_name', 'species', 'species_code', 'sex', 'age', 'tests',
            ]);

        return response()->json(['worklist' => $entries]);
    }

    /**
     * POST /api/lab/sync/manifest
     * Reconcile: the agent posts the uuids it holds; the cloud replies with
     * which of those it is still missing (so the agent can re-push them).
     */
    public function syncManifest(Request $request): JsonResponse
    {
        $agent = $this->agent($request);
        if (!$agent) {
            return $this->unauthorized();
        }

        $uuids = (array) $request->input('uuids', []);
        $known = LabTestSession::whereIn('uuid', $uuids)->pluck('uuid')->all();
        $missing = array_values(array_diff($uuids, $known));

        return response()->json([
            'missing'  => $missing,
            'received' => count($uuids),
        ]);
    }
}
