<?php

declare(strict_types=1);

namespace PpPhp\Modules\Licensing\Controllers;

use PpPhp\Core\Config;
use PpPhp\Core\Security;
use PpPhp\Core\Licensing\LicenseServer;
use PpPhp\Core\Licensing\LicenseKeys;
use PpPhp\Core\Update\AdvisoryStore;

/**
 * Owner console for composing Owner→client pushes — the notices and critical
 * patch instructions a licensed client receives on its next check-in.
 *
 * Owner-only, authority-only (this controller is stripped from client bundles).
 * The client never authors an advisory; it only ever verifies and acts on the
 * signed feed built from these rows.
 */
final class AdvisoryController
{
    public function __construct(private Config $config) {}

    private function gate(): void
    {
        Security::RequireAuth();
        if (!Security::IsOwner()) {
            redirect('/projects');
        }
    }

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

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

    private function checkCsrf(array $in): void
    {
        if (!Security::CSRF()->Check((string) ($in['_token'] ?? ''))) {
            $this->json(['success' => false, 'message' => 'Invalid security token. Reload and try again.'], 400);
        }
    }

    public function index(): string
    {
        $this->gate();

        $keys    = new LicenseKeys();
        $hasKey  = $keys->exists();
        $server  = new LicenseServer($keys);

        $advisories = AdvisoryStore::all();
        // The estate feeds the targeting picker — real instance ids + a friendly
        // label so the Owner aims a push without copying opaque ids by hand.
        $estate = $hasKey ? $server->estate(500) : [];
        $targets = [];
        foreach ($estate as $e) {
            $id = (string) ($e['instance_id'] ?? '');
            if ($id === '') continue;
            $targets[] = [
                'id'    => $id,
                'label' => trim((string) ($e['host'] ?? '') . ' · ' . (string) ($e['holder_name'] ?? '')) ?: $id,
                'status' => (string) ($e['status'] ?? ''),
            ];
        }

        $baseUrl   = $this->config->get('base_url');
        $assetsUrl = $this->config->get('assets_url');
        $user      = Security::CurrentUserData();
        $navItems  = \PpPhp\Core\ProjectNav::items('/licensing/advisories');
        $token     = Security::CSRF()->Token();

        ob_start();
        require __DIR__ . '/../Views/advisories.php';
        return (string) ob_get_clean();
    }

    /** Create or update an advisory. */
    public function save(): void
    {
        $this->gate();
        $in = $this->input();
        $this->checkCsrf($in);

        $title = trim((string) ($in['title'] ?? ''));
        if ($title === '') $this->json(['success' => false, 'message' => 'A title is required.'], 400);

        $action = ($in['action'] ?? 'notice') === 'apply' ? 'apply' : 'notice';
        $severity = (string) ($in['severity'] ?? 'info');
        // A forced-apply push must be critical — that is the only severity the
        // client will auto-install. Guard it here so the two can't disagree.
        if ($action === 'apply') $severity = 'critical';

        $audience = ($in['audience'] ?? 'all') === 'licenses' ? 'licenses' : 'all';
        $targets = [];
        foreach ((array) ($in['targets'] ?? []) as $t) {
            $t = trim((string) $t);
            if ($t !== '') $targets[] = $t;
        }
        if ($audience === 'licenses' && $targets === []) {
            $this->json(['success' => false, 'message' => 'Pick at least one installation, or set the audience to Everyone.'], 400);
        }

        $uid = AdvisoryStore::save([
            'uid'          => (string) ($in['uid'] ?? ''),
            'severity'     => $severity,
            'title'        => $title,
            'body'         => (string) ($in['body'] ?? ''),
            'link'         => (string) ($in['link'] ?? ''),
            'action'       => $action,
            'applyVersion' => (string) ($in['applyVersion'] ?? ''),
            'audience'     => $audience,
            'targets'      => $targets,
            'edition'      => (string) ($in['edition'] ?? ''),
            'status'       => ($in['status'] ?? 'live') === 'draft' ? 'draft' : 'live',
            'author'       => (string) (Security::CurrentUserData()['username'] ?? 'owner'),
            'expiresAt'    => trim((string) ($in['expiresAt'] ?? '')),
        ]);

        $this->json(['success' => true, 'uid' => $uid, 'message' => 'Advisory saved.']);
    }

    /** Retire (stop pushing) or re-activate an advisory. */
    public function status(): void
    {
        $this->gate();
        $in = $this->input();
        $this->checkCsrf($in);
        $uid = (string) ($in['uid'] ?? '');
        $status = (string) ($in['status'] ?? 'retired');
        if ($uid === '' || !AdvisoryStore::setStatus($uid, $status)) {
            $this->json(['success' => false, 'message' => 'Could not update that advisory.'], 400);
        }
        $this->json(['success' => true, 'message' => $status === 'retired' ? 'Advisory retired.' : 'Advisory re-activated.']);
    }

    public function delete(): void
    {
        $this->gate();
        $in = $this->input();
        $this->checkCsrf($in);
        $uid = (string) ($in['uid'] ?? '');
        if ($uid === '') $this->json(['success' => false, 'message' => 'No advisory specified.'], 400);
        AdvisoryStore::delete($uid);
        $this->json(['success' => true, 'message' => 'Advisory deleted.']);
    }
}
