<?php
declare(strict_types=1);

/**
 * Не показва PHP грешки към клиента.
 * Грешките трябва да се следят чрез server logs.
 */
ini_set('display_errors', '0');

/**
 * Зареждане на глобалната конфигурация.
 */
require_once '/etc/frontdoor/config.php';

/**
 * Инициализация на PHP session.
 */
if (session_status() !== PHP_SESSION_ACTIVE) {
    session_start();
}

/**
 * Всички отговори са JSON.
 */
header('Content-Type: application/json; charset=UTF-8');

/**
 * Зареждане на помощните модули.
 */
require_once __DIR__ . '/functions.php';
require_once __DIR__ . '/mail.php';
require_once __DIR__ . '/db.php';
require_once __DIR__ . '/certificate.php';

/**
 * Проверява дали flow е инициализиран чрез GET заявка.
 *
 * Без валидна GET инициализация POST заявките не се приемат.
 */
function ensure_session_initialized(): void
{
    if (!isset($_SESSION['flow_initialized']) || $_SESSION['flow_initialized'] !== true) {
        json_response(['ok' => false, 'error' => 'no active session flow'], 400);
    }
}

/**
 * Проверява дали е изминало минималното време
 * между GET и POST action=send_code.
 *
 * Защита срещу автоматизирани заявки.
 */
function ensure_send_code_allowed_by_time(): void
{
    if (!isset($_SESSION['get_request_unix_time'])) {
        json_response(['ok' => false, 'error' => 'missing GET initialization timestamp'], 400);
    }

    $elapsed = time() - (int)$_SESSION['get_request_unix_time'];

    if ($elapsed < MIN_POST_DELAY_SECONDS) {
        json_response([
            'ok' => false,
            'error' => 'POST request is too early',
            'seconds_elapsed' => $elapsed,
            'min_seconds_required' => MIN_POST_DELAY_SECONDS
        ], 429);
    }
}

/**
 * Проверява еднократния session verification token.
 *
 * В сесията се пази само hash на токена.
 * След успешна проверка токенът се маркира като използван.
 */
function ensure_valid_send_code_token(string $clientToken): void
{
    if (
        !isset($_SESSION['session_verification_token_hash']) ||
        !is_string($_SESSION['session_verification_token_hash'])
    ) {
        json_response(['ok' => false, 'error' => 'missing session verification token hash'], 400);
    }

    if (!password_verify($clientToken, $_SESSION['session_verification_token_hash'])) {
        json_response(['ok' => false, 'error' => 'session verification failed'], 403);
    }

    $_SESSION['session_verification_token_hash'] = null;
    $_SESSION['session_verification_token_used'] = true;
}

/**
 * Инициализира confirmation state в session.
 *
 * Кодът се пази само като bcrypt hash.
 */
function initialize_confirmation_state(string $email, string $code): void
{
    $_SESSION['confirmation_email'] = $email;
    $_SESSION['confirmation_code_hash'] = password_hash($code, PASSWORD_BCRYPT);
    $_SESSION['confirmation_code_created_at'] = time();
    $_SESSION['confirmation_attempts'] = 0;
    $_SESSION['confirmation_verified'] = false;
}

/**
 * Проверява дали confirmation flow е инициализиран.
 */
function ensure_confirmation_initialized(): void
{
    $required = [
        'confirmation_email',
        'confirmation_code_hash',
        'confirmation_code_created_at',
        'confirmation_attempts',
        'confirmation_verified'
    ];

    foreach ($required as $key) {
        if (!array_key_exists($key, $_SESSION)) {
            json_response(['ok' => false, 'error' => 'confirmation flow is not initialized'], 400);
        }
    }
}

/**
 * Забранява повторна verification операция.
 */
function ensure_confirmation_not_already_verified(): void
{
    if (!empty($_SESSION['confirmation_verified'])) {
        json_response(['ok' => false, 'error' => 'confirmation already completed'], 409);
    }
}

/**
 * Проверява дали confirmation code е изтекъл.
 */
function ensure_confirmation_code_not_expired(): void
{
    $age = time() - (int)$_SESSION['confirmation_code_created_at'];

    if ($age > CONFIRMATION_CODE_TTL_SECONDS) {

        /**
         * Изчистване на изтеклия код.
         */
        unset(
            $_SESSION['confirmation_code_hash'],
            $_SESSION['confirmation_code_created_at'],
            $_SESSION['confirmation_attempts']
        );

        json_response([
            'ok' => false,
            'error' => 'confirmation code expired',
            'ttl_seconds' => CONFIRMATION_CODE_TTL_SECONDS
        ], 410);
    }
}

/**
 * Проверява максималния брой опити.
 */
function ensure_attempt_limit_not_exceeded(): void
{
    $attempts = (int)$_SESSION['confirmation_attempts'];

    if ($attempts >= MAX_CONFIRMATION_ATTEMPTS) {
        json_response([
            'ok' => false,
            'error' => 'maximum confirmation attempts exceeded',
            'max_attempts' => MAX_CONFIRMATION_ATTEMPTS
        ], 429);
    }
}

/**
 * Проверява дали email съвпада с email-а,
 * използван при send_code.
 */
function verify_confirmation_email(string $email): void
{
    if (!hash_equals((string)$_SESSION['confirmation_email'], $email)) {
        json_response(['ok' => false, 'error' => 'email mismatch'], 403);
    }
}

/**
 * Проверява confirmation code.
 *
 * При неуспех увеличава броя опити.
 */
function verify_confirmation_code(string $clientCode): void
{
    $_SESSION['confirmation_attempts'] =
        (int)$_SESSION['confirmation_attempts'] + 1;

    if (!password_verify($clientCode, (string)$_SESSION['confirmation_code_hash'])) {

        $remaining =
            MAX_CONFIRMATION_ATTEMPTS - (int)$_SESSION['confirmation_attempts'];

        json_response([
            'ok' => false,
            'error' => 'invalid confirmation code',
            'remaining_attempts' => max(0, $remaining)
        ], 403);
    }
}

/**
 * Маркира confirmation flow като успешно завършен.
 */
function mark_confirmation_success(): void
{
    $_SESSION['confirmation_verified'] = true;

    /**
     * След успешна verification операция
     * кодът вече не е необходим.
     */
    unset(
        $_SESSION['confirmation_code_hash'],
        $_SESSION['confirmation_code_created_at'],
        $_SESSION['confirmation_attempts']
    );
}

/**
 * HTTP метод на текущата заявка.
 */
$method = $_SERVER['REQUEST_METHOD'] ?? '';

/**
 * Брояч за debug/диагностика.
 */
if (!isset($_SESSION['counter'])) {
    $_SESSION['counter'] = 0;
}

/**
 * Главен request router.
 */
switch ($method) {

    /**
     * GET:
     * Инициализира session flow.
     */
    case 'GET':

        $_SESSION['flow_initialized'] = true;

        /**
         * Генериране на еднократен verification token.
         */
        $token = generateVerificationToken();

        /**
         * В session се пази само hash на токена.
         */
        $_SESSION['session_verification_token_hash'] =
            password_hash($token, PASSWORD_BCRYPT);

        $_SESSION['session_verification_token_used'] = false;

        /**
         * Timestamp за anti-bot timing validation.
         */
        $_SESSION['get_request_unix_time'] = time();

        /**
         * Изчистване на стар confirmation flow.
         */
        unset(
            $_SESSION['confirmation_email'],
            $_SESSION['confirmation_code_hash'],
            $_SESSION['confirmation_code_created_at'],
            $_SESSION['confirmation_attempts'],
            $_SESSION['confirmation_verified']
        );

        json_response([
            'ok' => true,
            'method' => 'GET',
            'message' => 'session initialized',
            'session_id' => session_id(),
            'counter' => $_SESSION['counter'],
            'session_verification_token' => $token,
            'min_post_delay_seconds' => MIN_POST_DELAY_SECONDS,
            'confirmation_code_ttl_seconds' => CONFIRMATION_CODE_TTL_SECONDS,
            'max_confirmation_attempts' => MAX_CONFIRMATION_ATTEMPTS
        ]);

        break;

    /**
     * POST:
     * Основен API flow.
     */
    case 'POST':

        ensure_session_initialized();

        /**
         * Тип на операцията.
         */
        $action = require_post_param('action');

        /**
         * ============================================
         * action=send_code
         * ============================================
         */
        if ($action === 'send_code') {

            ensure_send_code_allowed_by_time();

            /**
             * Verification token е еднократен.
             */
            if (!empty($_SESSION['session_verification_token_used'])) {
                json_response([
                    'ok' => false,
                    'error' => 'session verification token already used'
                ], 409);
            }

            $clientToken =
                require_post_param('session_verification_token');

            $email =
                require_valid_email('email');

            ensure_valid_send_code_token($clientToken);

            /**
             * Генериране на confirmation code.
             */
            $code = generateConfirmationCode();

            initialize_confirmation_state($email, $code);

            /**
             * Изпращане на email.
             */
            if (SendConfirmCode($email, $code) !== true) {

                /**
                 * При проблем изчистваме confirmation state.
                 */
                unset(
                    $_SESSION['confirmation_email'],
                    $_SESSION['confirmation_code_hash'],
                    $_SESSION['confirmation_code_created_at'],
                    $_SESSION['confirmation_attempts'],
                    $_SESSION['confirmation_verified']
                );

                json_response([
                    'ok' => false,
                    'error' => 'SendConfirmCode failed'
                ], 500);
            }

            $_SESSION['counter']++;

            json_response([
                'ok' => true,
                'method' => 'POST',
                'action' => 'send_code',
                'message' => 'confirmation code sent',
                'session_id' => session_id(),
                'counter' => $_SESSION['counter'],
                'email' => $email,
                'confirmation_code_ttl_seconds' =>
                    CONFIRMATION_CODE_TTL_SECONDS
            ]);
        }

        /**
         * ============================================
         * action=verify_code
         * ============================================
         */
        if ($action === 'verify_code') {

            $email =
                require_valid_email('email');

            $clientCode =
                require_post_param('confirmation_code');

            /**
             * Common Name за сертификата.
             */
            $cn =
                require_post_param('cn');

            /**
             * Client CSR.
             */
            $csr =
                require_post_param('csr');

            /**
             * Request може да се идентифицира като
             * администратор, ако представи UUID (admin_pass)
             * предоставянето на SN е задължително за admin или client mode.
             */
            $uuid = null;
            $sn = null;

            if (
                isset($_POST['admin_pass']) &&
                trim((string)$_POST['admin_pass']) !== ''
            ) {
                $uuid = require_post_param('admin_pass');
            }
            if (
                isset($_POST['sn']) &&
                trim((string)$_POST['sn']) !== ''
            ) {
                $sn = require_post_param('sn');
            } else {

                json_response([
                    'ok' => false,
                    'error' => 'missing uuid or sn'
                ], 400);
            }

            /**
             * Проверки на confirmation flow.
             */
            ensure_confirmation_initialized();
            ensure_confirmation_not_already_verified();
            ensure_confirmation_code_not_expired();
            ensure_attempt_limit_not_exceeded();

            verify_confirmation_email($email);
            verify_confirmation_code($clientCode);

            /**
             * Проверка дали устройството е разрешено
             * за издаване на сертификат.
             */
            $device = findAllowedDeviceForCertificate(
                $email,
                $sn,
                $uuid
            );

            /**
             * Стабилно request name за certificate flow.
             */
            $requestName = makeRequestName(
                $device['email'],
                $device['device_id'],
                $device['kind']
            );

            /**
             * Подписване на клиентски сертификат.
             */
            $certResult = SignClientCertificate(
                $requestName,
                $cn,
                $email,
                $device['device_id'],
                $csr
            );

            /**
             * Анулиране на всички стари сертификати
             * за устройството revokeOldCertificatesForDevice(devId).
             * Replace with:
             */
            revokeOldCertificates(
                $email,
                $device['device_id']
            );

            /**
             * Записване на новия сертификат.
             */
            registerCertificate(
                $email,
                $device['device_id'],
                $cn,
                $certResult
            );

            /**
             * Регистрация/активиране на customer.
             *
             * В следваща версия тази функция
             * ще връща manager/guest flags.
             */
			$customerState = registerCustomerIfMissing(
								$email,
								$device['device_id'],
								$device['kind'] === 'admin'
			);

            /**
             * Confirmation flow приключва успешно.
             */
            mark_confirmation_success();

            $_SESSION['counter']++;

            /**
             * Успешен отговор към Android клиента.
             */
            json_response([
                'ok' => true,
                'method' => 'POST',
                'action' => 'verify_code',
                'message' => 'certificate signed',
                'signed' => $certResult['cert'],
                'cert_sn' => $certResult['cert_sn'],
                'thumbprint_sha256' => $certResult['thumbprint_sha256'],
                'valid_from' => $certResult['valid_from'],
                'valid_to' => $certResult['valid_to'],
				'manager' => $customerState['manager'],
				'guest' => $customerState['guest'],
            ]);
        }

        json_response([
            'ok' => false,
            'error' => 'unknown action'
        ], 400);

        break;

    default:

        json_response([
            'ok' => false,
            'error' => 'method not allowed'
        ], 405);
}
