<?php

namespace objects;

use classes\db;
use classes\object_property;
use Exception;
use traits\db_object_t;

class plate_scanners_o extends db
{
    use db_object_t;

    public object_property $department_id;
    public object_property $lane_id;
    public object_property $name;
    public object_property $notes;
    public object_property $api_key;

    private static bool $schemaInitialized = false;

    public function structure(): void
    {
        self::ensureSchema();
        $this->setTable('plate_scanners');
    }

    public function objectChanged(): void
    {
        // No need to invalidate the cache, since the plate_scanners object is not cached
    }

    public function getPlateScannerById(int $id): plate_scanners_o
    {
        global $db;
        // Get the record from the database
        $sql = "SELECT * FROM $this->table WHERE id = $id";
        $result = $db->query($sql);
        if ($result->num_rows > 0) {
            $this->id = $id;
            $this->getObjectProperties();
        }
        return $this;
    }

    public function getObjectProperties(): void
    {
        $this->department_id = new object_property($this->table, $this->id, 'department_id', 'int');
        $this->lane_id = new object_property($this->table, $this->id, 'lane_id', 'int');
        $this->name = new object_property($this->table, $this->id, 'name', 'string');
        $this->notes = new object_property($this->table, $this->id, 'notes', 'string');
        $this->api_key = new object_property($this->table, $this->id, 'api_key', 'string');
    }

    public function add(int $department_id, string $name, string $notes, ?int $lane_id = null): void
    {
        global $db, $response;
        try {
            // Generate an API key
            $api_key = bin2hex(random_bytes(32));
            $lane_id = $this->normalizeLaneId($department_id, $lane_id);
            // Avoid SQL injection
            $name = $db->escape_string($name);
            $notes = $db->escape_string($notes);
            $laneValue = $lane_id === null ? 'NULL' : (string)$lane_id;
            // Create a new record in the database
            $sql = "INSERT INTO $this->table (department_id, lane_id, name, notes, api_key) VALUES ($department_id, $laneValue, '$name', '$notes', '$api_key')";
            $db->query($sql);

            // Get the id of the new record
            $this->id = $db->insert_id();

            // Set the values of the object properties
            $this->getObjectProperties();
        } catch (\Exception $e) {
            $response->error($e->getMessage());
        }
    }

    public function edit(
        int $id,
        int $department_id,
        string $name,
        string $notes,
        ?int $lane_id = null,
        bool $laneIdProvided = false
    ): void
    {
        global $response;
        try {
            $this->select($id);
            // Use object_property setters so cached field values are invalidated before we serialize the scanner.
            $this->department_id->set($department_id);
            $this->name->set($name);
            $this->notes->set($notes);
            if ($laneIdProvided) {
                $lane_id = $this->normalizeLaneId($department_id, $lane_id);
                $this->lane_id->set($lane_id);
            }
        } catch (\Exception $e) {
            $response->error($e->getMessage());
        }
    }

    public function getPlateScannerByApiKey(mixed $token): plate_scanners_o
    {
        global $db;
        // Avoid SQL injection
        $token = $db->escape_string($token);
        // Get the record from the database
        $sql = "SELECT * FROM $this->table WHERE api_key = '$token'";
        $result = $db->query($sql);
        if ($result->num_rows > 0) {
            $this->id = $result->fetch_assoc()['id'];
            $this->getObjectProperties();
        }
        return $this;
    }

    /**
     * @return array{id:int,department_id:int,lane_id:int|null,name:string,notes:string,api_key:string}
     * @throws Exception
     */
    public function asArray(): array
    {
        $this->requireSelected();

        return [
            'id' => (int)$this->id,
            'department_id' => (int)$this->department_id->value(),
            'lane_id' => $this->lane_id->value() === null ? null : (int)$this->lane_id->value(),
            'name' => (string)$this->name->value(),
            'notes' => (string)$this->notes->value(),
            'api_key' => (string)$this->api_key->value(),
        ];
    }

    /**
     * @return array<int,plate_scanners_o>
     * @throws Exception
     */
    public function getDepartmentScanners(int $departmentId): array
    {
        $scanners = [];
        $rows = self::getFieldsWhere([
            'department_id' => $departmentId,
        ], ['id']);

        foreach ($rows as $row) {
            $scanner = (new plate_scanners_o())->select((int)$row['id']);
            if ($scanner->exists()) {
                $scanners[] = $scanner;
            }
        }

        return $scanners;
    }

    /**
     * @throws Exception
     */
    public function rotateApiKey(int $id): array
    {
        $scanner = $this->select($id);
        if (!$scanner->exists()) {
            throw new Exception('Number plate scanner not found');
        }

        $newApiKey = bin2hex(random_bytes(32));
        $scanner->api_key->set($newApiKey);

        return $scanner->asArray();
    }

    private function normalizeLaneId(int $departmentId, ?int $laneId): ?int
    {
        if ($laneId === null || $laneId <= 0) {
            return null;
        }

        $lane = (new department_lanes_o())->select($laneId);
        if (!$lane->exists()) {
            throw new Exception('Department lane not found');
        }

        if ((int)$lane->department->value() !== $departmentId) {
            throw new Exception('The lane does not belong to the number plate scanner department');
        }

        return (int)$lane->id;
    }

    private static function ensureSchema(): void
    {
        if (self::$schemaInitialized) {
            return;
        }

        global $db;

        if (!self::tableHasColumn('plate_scanners', 'lane_id')) {
            $db->query("ALTER TABLE `plate_scanners` ADD COLUMN `lane_id` INT NULL AFTER `department_id`");
        }

        self::$schemaInitialized = true;
    }

    private static function tableHasColumn(string $table, string $column): bool
    {
        global $db;

        $table = $db->escape_string($table);
        $column = $db->escape_string($column);
        $database = $db->escape_string($db->getDatabase());

        $result = $db->query(
            "SELECT COUNT(*) AS c
             FROM information_schema.COLUMNS
             WHERE TABLE_SCHEMA = '$database'
               AND TABLE_NAME = '$table'
               AND COLUMN_NAME = '$column'"
        );

        if (!$result) {
            return false;
        }

        $row = $result->fetch_assoc();
        return ((int)($row['c'] ?? 0)) > 0;
    }
}
