<?php

namespace objects;

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

class customer_password_reset_keys_o extends db
{
    use db_object_t;
    public object_property $customer_id;
    public object_property $note;
    public object_property $token;
    public object_property $created_at;
    public object_property $updated_at;
    public object_property $deleted_at;
    const TOKEN_LENGTH = 32;
    const TOKEN_EXPIRY_SECONDS = 3600; // 1 hour

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


    /**
     * Add a new customer reset key
     * @param array $data The properties
     * @returns void
     * @throws Exception If the object was not created successfully
     */
    public function add(array $data): void
    {
        $tmp_id = self::add_object($data);
        $this->id = $tmp_id;
        self::getObjectProperties();
        self::objectChanged();
    }

    /**
     * Generate a unique token
     * @param int $length The length of the token
     * @return string The generated token
     * @throws RandomException
     */
    public static function generateToken(int $length = self::TOKEN_LENGTH): string
    {
        return bin2hex(random_bytes($length / 2));
    }

    /**
     * Find a valid reset key by token
     * @param string $token The token to search for
     * @return customer_password_reset_keys_o|null The found reset key or null if not found
     * @throws Exception
     */
    public function findValidByToken(string $token): ?customer_password_reset_keys_o
    {
        global $db;
        // Sanitize input to conform to the expected format
        $token = preg_replace('/[^a-f0-9]/', '', strtolower($token));
        if (strlen($token) !== self::TOKEN_LENGTH) {
            return null;
        }
        // Query the database for a valid token
        $current_time = date('Y-m-d H:i:s');
        $sql = "SELECT id FROM $this->table WHERE token = '" . $db->escape_string($token) . "' AND deleted_at IS NULL AND created_at >= DATE_SUB('$current_time', INTERVAL " . self::TOKEN_EXPIRY_SECONDS . " SECOND) LIMIT 1";
        $result = $db->query($sql);
        if ($result->num_rows === 0) {
            return null;
        }
        $row = $result->fetch_assoc();
        $this->id = (int)$row['id'];
        $this->getObjectProperties();
        return $this;
    }

    /**
     * Check if the token is valid and not expired
     * @return bool True if the token is valid, false otherwise
     * @throws Exception
     */
    public function isValidToken(): bool
    {
        self::requireSelected();
        $created_at = strtotime($this->created_at->value());
        $current_time = time();
        return (
            ($current_time - $created_at) <= self::TOKEN_EXPIRY_SECONDS) &&
            ($this->deleted_at->value() === null
            );
    }

    /**
     * Require that the token is valid and not expired
     * @throws Exception If the token is invalid or expired
     */
    public function requireValidToken(): void
    {
        self::requireSelected();
        if (!$this->isValidToken()) {
            throw new Exception('Invalid or expired token');
        }
    }

    /**
     * Set the password of the customer associated with this reset key
     * @param string $new_password The new password to set
     * @return void
     * @throws Exception If the token is invalid or expired
     */
    public function setPassword(string $new_password): void
    {
        self::requireSelected();
        self::requireValidToken();
        $customer = new users_o();
        $customer->getUserByCustomerNumber((int)$this->customer_id->value());
        $customer->requireSelected();
        $customer->setPassword($new_password);
        $this->delete();
    }

    /**
     * @return void
     */
    public function getObjectProperties(): void
    {
        $this->customer_id = new object_property($this->table, $this->id, 'customer_id', 'int', false);
        $this->note = new object_property($this->table, $this->id, 'note', 'string', false);
        $this->token = new object_property($this->table, $this->id, 'token', 'string', false);
        $this->created_at = new object_property($this->table, $this->id, 'created_at', 'string', false);
        $this->updated_at = new object_property($this->table, $this->id, 'updated_at', 'string', false);
        $this->deleted_at = new object_property($this->table, $this->id, 'deleted_at', 'string', false);
    }

    public function objectChanged(): void
    {
        //TODO: Add cache invalidation
    }
}