<?php

namespace App\Http\Controllers\TerminalSession;

use App\Http\Requests\InitTerminalSessionRequest;
use App\Models\AccessControl;
use App\Models\Computer;
use App\Models\Enum\BookingStatusEnum;
use App\Models\Enum\ComputerStatusEnum;
use App\Models\User;
use App\Models\UserTypeRule;
use Carbon\Carbon;

class InitTerminalSessionController
{
    private const DEFAULT_TIME_SYNCHRONIZATION_EPSILON = 5;

    public function initTerminalSessionAction(InitTerminalSessionRequest $request)
    {
        $initTerminalSessionRequestBody = $request->getData();

        if (!$this->validateTimeSynchronization($initTerminalSessionRequestBody->getLocalTime())) {
            return response()->json(["message" => "Local Time is not synchronized with the server time"], 400);
        }

        $user = $this->findUser(
            $initTerminalSessionRequestBody->getPersonIdentifier(),
            $initTerminalSessionRequestBody->getPIN()
        );
        if (!$user) {
            return response()->json(["message" => "User not found"], 404);
        }

        if ($user->lock_date_until !== null && now() < $user->lock_date_until) {
            return response()->json(
                ["message" => "User is locked until: ". $user->lock_date_until->format("d/m/Y H:i:s")],
                403
            );
        }

        if ($user->locked === true) {
            return response()->json(["message" => "User is locked"], 403);
        }

        $now = Carbon::now();

        $currentBooking = $user->bookings()
            ->where("start_time", "<=", $now)
            ->where("end_time", ">", $now)
            ->where("user_id", $user->id)
            ->first()
        ;
        if (!$currentBooking) {
            return response()->json(["message" => "Booking not found for now"], 403);
        }

        if ($currentBooking->booking_status_id !== BookingStatusEnum::ID[BookingStatusEnum::CONFIRMED]) {
            return response()->json(["message" => "Booking not confirmed"], 403);
        }

        $computer = Computer::where("mac_address", $initTerminalSessionRequestBody->getMacAddress())->first();
        if (!$computer) {
            return response()->json(["message" => "Computer not found"], 404);
        }

        $assignedComputer = $currentBooking->computer;

        if ($computer->id !== $assignedComputer->id) {
            return response()->json(["message" => "Your booking is not for this computer"], 403);
        }

        if ($assignedComputer->status->id !== ComputerStatusEnum::ID[ComputerStatusEnum::ACTIVE]) {
            return response()->json(["message" => "Computer not available"], 403);
        }

        $accessControls = AccessControl::where("user_type_id", $user->user_type_id)
            ->where("active_from_date", "<=", $now)
            ->where("deactivate_date", ">=", $now)
            ->get()
            ->pluck("name", "white_list", "black_list", "blocked_apps")
            ->toArray()
        ;
        $userTypeRules = UserTypeRule::where("user_type_id", $user->user_type_id)
            ->where("active_from_date", "<=", $now)
            ->where("deactivate_date", ">=", $now)
            ->get()
            ->pluck("name", "parameter", "value")
            ->toArray()
        ;

        $userSession = $currentBooking->userSession;

        if ((bool) $userSession->user_authenticated) {
            return response()->json(["message" => "Forbidden init this user session"], 403);
        }

        $userSession->start_time = $now;
        $userSession->user_authenticated = true;

        $userSession->save();

        return response()->json([
            "start_at" => Carbon::parse($currentBooking->start_time)->toIso8601String(),
            "end_time" => Carbon::parse($currentBooking->end_time)->toIso8601String(),
            "access_controls" => $accessControls,
            "user_type_rules" => $userTypeRules,
        ]);
    }

    private function validateTimeSynchronization($localTime): bool
    {
        $serverTime = Carbon::now();
        $localTime = Carbon::parse($localTime);

        $diffInSeconds = $serverTime->diffInSeconds($localTime, false);

        return abs($diffInSeconds) <= self::DEFAULT_TIME_SYNCHRONIZATION_EPSILON * 60;
    }

    private function findUser($personIdentifier, $pin): ?User
    {
        /** @var User $user */
        $user = User::where("library_identifier", $personIdentifier)
            ->orWhere("identifier", $personIdentifier)
            ->first()
        ;

        return $user ?? null;
    }
}
