<?php

namespace classes;

use classes\totp;
use Exception;
use interfaces\authentication_i;
use objects\plate_scanners_o;
use objects\tokens_o;
use objects\users_o;
use objects\subusers_o;

class authentication implements authentication_i
{

    /**
     * @throws Exception
     */
    public function authenticate(int $customer_number, string $password): bool
    {
        // Get the customer from the database
        $customer = (new users_o())->getUserByCustomerNumber($customer_number);
        // Check if the customer exists
        if (!$customer->exists()) {
            return false;
        }
        // Check if the customer has a password
        if (!$customer->hasPassword()) {
            // If the customer doesn't have a password, we can't authenticate them, so we return false
            return false;
        }
        // Check if the password is correct
        if (!$this->match_passwords($password, $customer->getPassword())) {
            return false;
        }
        return true;
    }

    public function is_2fa_enabled(users_o|subusers_o $user): bool
    {
        return $user->isTwoFactorEnabled();
    }

    public function create_2fa_token(int $id, string $type): string
    {
        // Create a temporary 2FA token
        $token = bin2hex(random_bytes(32));
        (new tokens_o())->create($id, $token, $type);
        return $token;
    }

    public function verify_2fa_code(users_o|subusers_o $user, string $code): bool
    {
        $secret = $user->getTwoFactorSecret();
        if (!$secret) {
            return false;
        }
        return (new totp())->verifyCode($secret, $code);
    }

    public function match_passwords($password, $hash): bool
    {
        // Compare the password with the hash
        return password_verify($password, $hash);
    }

    public function create_token(int $customer_number): string
    {
        // Create a token
        $token = bin2hex(random_bytes(32));
        // Resolve the user by customer number and ensure it exists to avoid accessing an uninitialized typed property
        $user = (new users_o())->getUserByCustomerNumber($customer_number);
        if (!$user->exists()) {
            throw new \Exception('User not found for customer number: ' . $customer_number);
        }
        $user_id = $user->id;
        // Save the token in the database
        (new tokens_o())->create($user_id, $token, 'AUTH_TOKEN');
        return $token;
    }

    public function create_token_by_user_id(int $user_id): string
    {
        // Create a token
        $token = bin2hex(random_bytes(32));
        // Save the token in the database
        (new tokens_o())->create($user_id, $token, 'AUTH_TOKEN');
        return $token;
    }

    public function create_employee_token(int $employee_id): string
    {
        // Create a token
        $token = bin2hex(random_bytes(32));
        // Save the token in the database
        (new tokens_o())->create($employee_id, $token, 'AUTH_TOKEN');
        return $token;
    }

    public function validate_token(string $token): bool
    {
        // First: try validating as a classic user auth token
        $dbToken = (new tokens_o())->getToken($token);
        if ($dbToken && $dbToken->id) {
            return true;
        }
        // Fallback: try validating as a subuser session token
        $subuser = (new subusers_o())->getSubuserBySessionToken($token);
        if ($subuser !== null) {
            return true;
        }
        return false;
    }

    /**
     * @throws Exception
     */
    public function get_user(): users_o|false
    {
        /**
         * Get the user from the token
         */
        // Get the token from the headers
        $headers = getallheaders();
        if (!isset($headers['Authorization'])) {
            return false;
        }
        $token = $headers['Authorization'];
        // Strip the Bearer prefix
        $token = str_replace('Bearer ', '', $token);
        // Get the token from the database
        $token = (new tokens_o())->getToken($token);
        // Check if the token exists
        if (!$token->id) {
            return false;
        }
        if ($token->type->value() === "AUTH_TOKEN_SUBUSER") {
            // Get the customer number from the headers
            if (!isset($headers['X-Customer-Number'])) {
                return false;
            }
            $customer_number = (int)$headers['X-Customer-Number'];
            // Get the user by the customer number
            return (new users_o())->getUserByCustomerNumber($customer_number);
        }
        // Get the user from the database
        return (new users_o())->getUserById($token->user_id->value());
    }

    public function get_plate_scanner(): plate_scanners_o|false
    {
        // Get the token from the headers
        $headers = getallheaders();
        $tmp = json_decode(file_get_contents('php://input'), true);
        if (!is_array($tmp)) {
            $tmp = [];
        }
        if (!isset($headers['Authorization']) && !isset($_GET['token']) && !isset($_POST['token']) && !isset($tmp['token'])) {
            return false;
        }
        $token = $_GET['token'] ?? $headers['Authorization'] ?? $tmp['token'] ?? $_POST['token'];
        // Strip the Bearer prefix (If the token is from the headers)
        if (isset($headers['Authorization'])) {
            $token = str_replace('Bearer ', '', $token);
        }
        // Get the token from the database
        $token = (new plate_scanners_o())->getPlateScannerByApiKey($token);
        // Check if the token exists
        if (!isset($token->id)) {
            return false;
        }
        // Get the plate scanner from the database
        return $token;
    }

    /**
     * @throws Exception
     */
    public function get_subuser(): subusers_o|false
    {
        // Try to resolve a subuser from an incoming bearer token or explicit token parameter
        $headers = getallheaders();
        $tmp = json_decode(file_get_contents('php://input'), true);
        if (!is_array($tmp)) {
            $tmp = [];
        }
        if (!isset($headers['Authorization']) && !isset($_GET['token']) && !isset($_POST['token']) && !isset($tmp['token'])) {
            return false;
        }
        $token = $_GET['token'] ?? $headers['Authorization'] ?? $tmp['token'] ?? $_POST['token'];
        // Strip the Bearer prefix (If the token is from the headers)
        if (isset($headers['Authorization'])) {
            $token = str_replace('Bearer ', '', $token);
        }
        // Resolve subuser session from cache
        $subuser = (new subusers_o())->getSubuserBySessionToken($token);
        if ($subuser === null) {
            return false;
        }
        return $subuser;
    }

    public function hash_password($password): string
    {
        // Hash the password
        return password_hash($password, PASSWORD_DEFAULT);
    }

    public function authenticateEmployee(int $user_id, string $password): bool
    {
        // Get the employee from the database
        $employee = (new users_o())->getUserById($user_id);
        // Check if the employee exists
        if (!$employee->exists()) {
            return false;
        }
        // Check if the password is correct
        if (!$this->match_passwords($password, $employee->getPassword())) {
            return false;
        }
        return true;
    }

    public function get_subuser_customer_number_target(): int|false
    {
        /**
         * Decode the headers
         */
        $headers = getallheaders();
        if (!isset($headers['X-Customer-Number'])) {
            return false;
        }
        return (int)$headers['X-Customer-Number'];
    }
}