<?php

declare(strict_types=1);

namespace PpPhp\Modules\Projects\Controllers;

use PpPhp\Core\Licensing\LicenseKeys;
use PpPhp\Core\Licensing\LicenseServer;
use PpPhp\Core\Update\UpdateProtocol;
use PpPhp\Core\Projects\BuiltinTemplates;

/**
 * AUTHORITY side of the remote template gallery. Runs only on an update/licence
 * authority (dev box, primary, secondary) — it holds the Ed25519 signing key.
 * Client installs strip this controller (serverOnlyFiles) since they never serve
 * a feed; they FETCH it via core/Projects/TemplateClient.
 *
 * Two endpoints, both licence-gated exactly like the Update Engine's feed:
 *   • catalog() — a SIGNED list of templates the caller may use (domain-separated
 *     prefix PPT1, verified offline by the client against the pinned public key).
 *   • fetch()   — streams a single template's package (or its content seed), also
 *     licence + role gated.
 *
 * Distribution rule: OWNER-ONLY templates (audience === ['owner']) are the
 * authority's own assets (e.g. the data-bearing Full Restore of *our* community)
 * and are NEVER served to clients — only templates with a non-owner role in their
 * audience are distributable. Role is client-asserted (the authority only knows
 * the install's licence, not its users), so this exclusion is the real boundary;
 * everything served is distributable by design.
 */
class TemplateFeedController
{
    private const PREFIX = 'PPT1';

    private function input(): array
    {
        $raw = file_get_contents('php://input');
        $d = json_decode((string) $raw, true);
        return is_array($d) ? $d : $_POST;
    }

    private function json(array $payload, int $status = 200): never
    {
        http_response_code($status);
        header('Content-Type: application/json');
        echo json_encode($payload);
        exit;
    }

    private function normalizeRole(string $r): string
    {
        return in_array($r, BuiltinTemplates::ROLES, true) ? $r : 'user';
    }

    /** A template is distributable to clients when its audience includes a non-owner role. */
    private function distributable(array $roles): bool
    {
        return array_values(array_diff($roles, ['owner'])) !== [];
    }

    /** Signed, licence-gated catalog of templates the caller's role may use. */
    public function catalog(): never
    {
        $in = $this->input();
        $keys = new LicenseKeys();
        if (!$keys->exists()) {
            $this->json(['ok' => true, 'configured' => false, 'message' => 'This server is not configured to publish templates.']);
        }
        if (!(new LicenseServer($keys))->isEntitled((string) ($in['instanceId'] ?? ''), (string) ($in['secret'] ?? ''))) {
            $this->json(['ok' => true, 'configured' => true, 'licensed' => false,
                'message' => 'A genuine, active licence is required to load templates. Check your licence status, then try again.']);
        }

        $role = $this->normalizeRole((string) ($in['role'] ?? 'user'));
        $items = [];
        foreach (BuiltinTemplates::all() as $t) {
            $roles = (array) ($t['roles'] ?? []);
            if (!$this->distributable($roles) || !in_array($role, $roles, true)) {
                continue;
            }
            $pkg  = (string) $t['package_path'];
            $seed = $t['seed_path'] ?? null;
            $items[] = [
                'id' => $t['id'], 'name' => $t['name'], 'slug' => $t['slug'], 'description' => $t['description'],
                'category' => $t['category'], 'tags' => $t['tags'], 'icon' => $t['icon'], 'color' => $t['color'],
                'roles' => $roles, 'audience' => $t['audience'], 'has_content' => (bool) $t['has_content'],
                'code_files' => (int) ($t['content']['executable_custom_files'] ?? 1),
                'pkg'  => ['sha256' => (string) hash_file('sha256', $pkg), 'size' => (int) filesize($pkg)],
                'seed' => ($seed && is_file($seed)) ? ['sha256' => (string) hash_file('sha256', $seed), 'size' => (int) filesize($seed)] : null,
            ];
        }
        $token = UpdateProtocol::sign(['templates' => $items, 'role' => $role, 'iat' => time()], $keys->secretKey(), self::PREFIX);
        $this->json(['ok' => true, 'configured' => true, 'licensed' => true, 'catalog' => $token]);
    }

    /** Licence + role gated download of a template's package ('pkg') or content seed ('seed'). */
    public function fetch(string $id): never
    {
        $in = $this->input();
        $keys = new LicenseKeys();
        if (!$keys->exists()) {
            $this->json(['ok' => false, 'message' => 'Templates are not published here.'], 404);
        }
        if (!(new LicenseServer($keys))->isEntitled((string) ($in['instanceId'] ?? ''), (string) ($in['secret'] ?? ''))) {
            $this->json(['ok' => false, 'message' => 'A genuine, active licence is required.'], 403);
        }
        $role = $this->normalizeRole((string) ($in['role'] ?? 'user'));
        $t = BuiltinTemplates::find($id);
        if ($t === null || !$this->distributable((array) $t['roles']) || !in_array($role, (array) $t['roles'], true)) {
            $this->json(['ok' => false, 'message' => 'Template not available to your role.'], 404);
        }
        $which = (($in['which'] ?? 'pkg') === 'seed') ? 'seed' : 'pkg';
        $path = $which === 'seed' ? (string) ($t['seed_path'] ?? '') : (string) $t['package_path'];
        if ($path === '' || !is_file($path)) {
            $this->json(['ok' => false, 'message' => 'That file is not available.'], 404);
        }
        header('Content-Type: application/octet-stream');
        header('Content-Length: ' . (string) filesize($path));
        header('X-Content-Type-Options: nosniff');
        header('Cache-Control: private, no-store');
        readfile($path);
        exit;
    }
}
