<?php

declare(strict_types=1);

namespace PpPhp\Core\Auth;

use PpPhp\Core\DB;
use PpPhp\Core\Notification;

final class TemporaryAccessRequestService
{
    private static bool $schemaReady = false;

    public static function schemaSql(): string
    {
        return "CREATE TABLE IF NOT EXISTS `temporary_access_requests` (
            `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
            `full_name` VARCHAR(140) NOT NULL,
            `email` VARCHAR(190) NOT NULL,
            `organization` VARCHAR(180) DEFAULT NULL,
            `role_title` VARCHAR(140) DEFAULT NULL,
            `use_case` TEXT NOT NULL,
            `requested_duration_hours` SMALLINT UNSIGNED NOT NULL DEFAULT 24,
            `status` ENUM('pending','approved','declined','expired') NOT NULL DEFAULT 'pending',
            `request_ip_hash` CHAR(64) DEFAULT NULL,
            `user_agent` VARCHAR(500) DEFAULT NULL,
            `reviewed_by` INT DEFAULT NULL,
            `reviewed_at` DATETIME DEFAULT NULL,
            `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
            `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            INDEX `idx_temp_access_status_created` (`status`, `created_at`),
            INDEX `idx_temp_access_email` (`email`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
    }

    public static function ensureSchema(): void
    {
        if (self::$schemaReady) {
            return;
        }

        DB::Init();
        DB::SetConnection();
        DB::Exec(self::schemaSql());
        if (DB::LastError() !== '') {
            throw new \RuntimeException('Temporary access requests could not be prepared.');
        }

        Notification::Enable('notifications');
        if (DB::LastError() !== '') {
            throw new \RuntimeException('Administrator notifications could not be prepared.');
        }
        Notification::SetTableName('notifications');
        self::$schemaReady = true;
    }

    /**
     * Store a request and create an addressed in-app notification for every
     * superadministrator. Both operations commit together.
     */
    public static function submit(array $data): int
    {
        self::ensureSchema();
        $pdo = DB::Pdo();
        $pdo->beginTransaction();

        try {
            $requestId = DB::Insert('temporary_access_requests', [
                'full_name' => (string)$data['full_name'],
                'email' => (string)$data['email'],
                'organization' => self::nullable($data['organization'] ?? null),
                'role_title' => self::nullable($data['role_title'] ?? null),
                'use_case' => (string)$data['use_case'],
                'requested_duration_hours' => (int)$data['requested_duration_hours'],
                'status' => 'pending',
                'request_ip_hash' => self::nullable($data['request_ip_hash'] ?? null),
                'user_agent' => self::nullable($data['user_agent'] ?? null),
            ]);

            if ($requestId === false) {
                throw new \RuntimeException('The temporary access request could not be saved.');
            }

            $admins = DB::Select('users', ['role' => 'superadmin']);
            $recipients = is_array($admins) && $admins !== [] ? $admins : [['id' => null]];
            $duration = self::durationLabel((int)$data['requested_duration_hours']);
            $message = sprintf(
                '%s (%s) requested %s of temporary PPPHP access.',
                (string)$data['full_name'],
                (string)$data['email'],
                $duration
            );

            foreach ($recipients as $admin) {
                $notificationId = Notification::Add([
                    'title' => 'Temporary access requested',
                    'message' => $message,
                    'icon' => 'person-plus',
                    'url' => '/users?temporary_request=' . $requestId,
                    'userId' => isset($admin['id']) ? (int)$admin['id'] : null,
                ]);
                if ($notificationId === false) {
                    throw new \RuntimeException('The administrator notification could not be created.');
                }
            }

            $pdo->commit();
            return (int)$requestId;
        } catch (\Throwable $exception) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            throw $exception;
        }
    }

    private static function nullable(mixed $value): ?string
    {
        $value = trim((string)$value);
        return $value === '' ? null : $value;
    }

    /** Human wording for a requested window — shared with the email templates. */
    public static function durationLabel(int $hours): string
    {
        return match ($hours) {
            72 => '3 days',
            168 => '7 days',
            default => '24 hours',
        };
    }

    /**
     * Requests still waiting on a decision, oldest first — whoever has been
     * waiting longest deserves an answer first.
     *
     * @return array<int, array<string, mixed>>
     */
    public static function pending(): array
    {
        self::ensureSchema();

        return DB::FetchAll(
            "SELECT * FROM `temporary_access_requests` WHERE `status` = 'pending' ORDER BY `created_at` ASC"
        );
    }

    /** @return array<string, mixed>|null */
    public static function find(int $id): ?array
    {
        self::ensureSchema();

        $row = DB::Query('SELECT * FROM `temporary_access_requests` WHERE `id` = ?', [$id])->fetchAssoc();

        return $row ?: null;
    }

    /**
     * Record a decision. Times come from MySQL rather than PHP — the two clocks
     * do not agree on this host, and a request must never look reviewed in the future.
     */
    public static function review(int $id, string $status, int $reviewerId): bool
    {
        if (!in_array($status, ['approved', 'declined'], true)) {
            return false;
        }
        self::ensureSchema();

        DB::Query(
            'UPDATE `temporary_access_requests`
                SET `status` = ?, `reviewed_by` = ?, `reviewed_at` = NOW()
              WHERE `id` = ? AND `status` = \'pending\'',
            [$status, $reviewerId, $id]
        );

        return true;
    }
}

