<?php

namespace traits;

use Exception;
use motorapi\actions\license_plate_lookup_a;
use objects\module_action_logs_o;

trait module_action_t
{
    /**
     * The module name
     * @var string The module name
     */
    private string $module = '';
    /**
     * The modules valid actions
     * @var array The modules valid actions
     */
    private array $actions = [];
    /**
     * The relevant data for the action (Only set when the action is called)
     * @var array Data for the action
     */
    private array $data = [];
    /**
     * The HTTP status code, if not set, defaults to 0
     * @var int HTTP status code
     */
    private int $status = 0;
    /**
     * The action name (Only set when the action is called)
     * @var string The action name
     * @see set_action() - Sets the action name
     * @see get_action() - Gets the action name
     */
    private string $action = '';

    /**
     * Constructor
     * @throws Exception If the module name is invalid
     * @throws Exception If the action name is invalid
     * @throws Exception If the action is not valid
     */
    public function __construct()
    {
        // Run the run method
        self::run();
    }

    /**
     * The run method, to be implemented by the module
     * This should be used to define the following:
     * - The module name
     * - The actions
     * @return void
     */
    abstract public function run(): void;

    /**
     * Set the action name
     * @param string $action The action name
     * @return module_action_t|license_plate_lookup_a
     * @throws Exception If the action name is invalid
     */
    public function set_action(string $action): self
    {
        self::validate_action_name($action);
        $this->action = strtoupper($action);
        return $this;
    }

    /**
     * Validate the action name
     * @param string $action The action name
     * @return void
     * @throws Exception If the action name is empty
     * @throws Exception If the action name is invalid (only letters, numbers and underscores are allowed)
     * @throws Exception If the action name is longer than 50 characters
     * @throws Exception If the action name is shorter than 3 characters
     */
    private static function validate_action_name(string $action): void
    {
        // Action name cannot be empty
        if (empty($action)) {
            throw new Exception('Action name cannot be empty');
        }
        // Action name can only contain letters, numbers and underscores
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $action)) {
            throw new Exception('Action name can only contain letters, numbers and underscores');
        }
        // Action name cannot be longer than 50 characters
        if (strlen($action) > 50) {
            throw new Exception('Action name cannot be longer than 50 characters');
        }
        // Action name cannot be shorter than 3 characters
        if (strlen($action) < 3) {
            throw new Exception('Action name cannot be shorter than 3 characters');
        }
    }

    /**
     * Set the module name
     * @param string $module The module name
     * @return module_action_t|license_plate_lookup_a
     * @throws Exception If the module name is invalid
     */
    public function set_module_name(string $module): self
    {
        self::validate_module_name($module);
        $this->module = strtoupper($module);
        return $this;
    }

    /**
     * Validate the module name
     * @param string $module The module name
     * @return void
     * @throws Exception If the module name is empty
     * @throws Exception If the module name is invalid (only letters, numbers and underscores are allowed)
     * @throws Exception If the module name is longer than 50 characters
     * @throws Exception If the module name is shorter than 3 characters
     */
    private static function validate_module_name(string $module): void
    {
        // Module name cannot be empty
        if (empty($module)) {
            throw new Exception('Module name cannot be empty');
        }
        // Module name can only contain letters, numbers and underscores
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $module)) {
            throw new Exception('Module name can only contain letters, numbers and underscores');
        }
        // Module name cannot be longer than 50 characters
        if (strlen($module) > 50) {
            throw new Exception('Module name cannot be longer than 50 characters');
        }
        // Module name cannot be shorter than 3 characters
        if (strlen($module) < 3) {
            throw new Exception('Module name cannot be shorter than 3 characters');
        }
    }

    /**
     * Add an action to the module
     * @param string $action The action name
     * @param string $description The action description
     * @param callable $callback The action callback function
     * @param array $parameters The action parameters
     * @return module_action_t|license_plate_lookup_a
     * @throws Exception If the action name is invalid
     */
    public function add_action(string $action, string $description, callable $callback, array $parameters = []): self
    {
        // Validate the action name
        self::validate_action_name($action);
        // Check if the action name is already set
        if (isset($this->actions[$action])) {
            throw new Exception('Action name is already set');
        }
        // Check if the action callback function is callable
        if (!is_callable($callback)) {
            throw new Exception('Action callback function is not callable');
        }
        // Check if the action parameters are an array
        if (!is_array($parameters)) {
            throw new Exception('Action parameters are not an array');
        }
        // Add the action to the module
        $this->actions[$action] = [
            'description' => $description,
            'callback' => $callback,
            'parameters' => $parameters
        ];
        return $this;
    }

    /**
     * Get the action name
     * @return string The action name
     */
    public function get_action_name(): string
    {
        return $this->action;
    }

    /**
     * Get the actions
     * @return array The actions
     */
    public function get_actions(): array
    {
        return $this->actions;
    }

    /**
     * Add data to the action
     * @param array $data The data to be added
     * @return self
     */
    public function add_data(array $data): self
    {
        $this->data = array_merge($this->data, $data);
        return $this;
    }

    /**
     * Get the HTTP status code
     * @return int The HTTP status code
     */
    public function get_status(): int
    {
        return $this->status;
    }

    /**
     * Set the HTTP status code
     * @param int $status The HTTP status code
     * @return self
     */
    public function set_status(int $status): self
    {
        $this->status = $status;
        return $this;
    }

    /**
     * Add action log to the database
     * @param string $action The action name
     * @param int $status The action status code (HTTP status code, e.g. 200, 404, 500)
     * @return self
     * @throws Exception If the action name is empty
     * @throws Exception If the action status code is empty
     * @throws Exception If the object was not created successfully
     */
    public function add_action_log(string $action, int $status = 0): self
    {
        // Validate the action name
        if (empty($action)) {
            throw new Exception('Action name cannot be empty');
        }
        // Validate the action status code
        if (empty($status)) {
            throw new Exception('Action status code cannot be empty');
        }

        // Add action log to the database
        $module_action_log = new module_action_logs_o();
        $module_action_log->add(self::get_module_name(), (string)$action, (int)$status, self::get_data());
        return $this;
    }

    /**
     * Get the module name
     * @return string The module name
     * @throws Exception If the module name isn't set
     */
    public function get_module_name(): string
    {
        if (empty($this->module)) {
            throw new Exception('Module name is not set');
        }
        return $this->module;
    }

    /**
     * Get the data
     * @return array The data
     */
    public function get_data(): array
    {
        return $this->data;
    }

    /**
     * Set the data
     * @param array $data The data to be returned
     * @return self
     */
    public function set_data(array $data): self
    {
        $this->data = $data;
        return $this;
    }
}