<?php

declare(strict_types=1);

namespace PpPhp\Modules\Licensing\Controllers;

use PpPhp\Core\Config;
use PpPhp\Core\Licensing\LicenseClient;
use PpPhp\Core\Licensing\LicenseException;
use PpPhp\Core\Licensing\LicenseGate;
use PpPhp\Core\Licensing\LicenseProtocol;
use PpPhp\Core\Licensing\LicenseTrust;
use PpPhp\Core\Security;

/**
 * THIS installation's licence page — activate, see status, release the seat.
 *
 * Administrator-level, not owner-level: the person running a hosted copy has to
 * be able to enter the key they were sold without the vendor's account. (Issuing
 * licences is the other controller, and that one is owner-only.)
 *
 * Reachable even while the console is locked — LicenseGate allows /licensing
 * through, because a page that says "you are not licensed" is useless if it is
 * behind the lock it is explaining.
 */
class LicenseController
{
    public function __construct(private Config $config) {}

    private function gate(): void
    {
        Security::RequireAuth();
        if (!Security::IsAdmin()) {
            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 checkToken(array $input): void
    {
        if (!Security::CSRF()->Check((string) ($input['_token'] ?? ''))) {
            $this->json(['success' => false, 'message' => 'Invalid security token. Reload the page and try again.'], 400);
        }
    }

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

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

        $client   = new LicenseClient();
        $status   = $client->status();
        $enforced = LicenseGate::enforced();
        $stamped  = LicenseTrust::isStamped();
        $locked   = LicenseGate::locked();
        // A backup/secondary server is failover infrastructure — it needs no
        // licence of its own, so the page explains that instead of prompting to
        // activate. (enforced() is already false on a backup for the same reason.)
        // ServerRole is STRIPPED from client bundles, so guard it — a customer
        // copy is never a backup, and the page must render, not 500.
        $hasRole     = class_exists(\PpPhp\Core\Licensing\ServerRole::class);
        $isBackup    = $hasRole && \PpPhp\Core\Licensing\ServerRole::isBackup();
        $roleMainUrl = $hasRole ? \PpPhp\Core\Licensing\ServerRole::mainUrl() : '';
        // Does THIS install hold a signing key? If so it is the licence authority
        // (main), not a customer copy — the page says so instead of prompting to
        // activate a licence against itself.
        $isAuthority = LicenseGate::isAuthority();

        $baseUrl   = $this->config->get('base_url');
        $assetsUrl = $this->config->get('assets_url');
        $user      = Security::CurrentUserData();
        $isOwner   = Security::IsOwner();

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

    /** Redeem an activation code. */
    public function activate(): void
    {
        $this->gate();
        $in = $this->input();
        $this->checkToken($in);

        try {
            $result = (new LicenseClient())->activate(
                (string) ($in['code'] ?? ''),
                (string) ($in['email'] ?? ''),
                (string) ($in['label'] ?? '')
            );
        } catch (LicenseException $e) {
            $this->json(['success' => false, 'message' => $e->getMessage()], 400);
        } catch (\Throwable $e) {
            $this->json(['success' => false, 'message' => 'Activation failed: ' . $e->getMessage()], 500);
        }

        $claims = $result['claims'];
        $this->json([
            'success' => true,
            'message' => !empty($result['reused'])
                ? 'Re-activated — this machine already had a seat, so none was used up.'
                : 'Activated. This installation is licensed to ' . ($claims['holder'] ?: 'you') . '.',
            'status'  => (new LicenseClient())->status(),
        ]);
    }

    /** Pull a fresh lease now rather than waiting for the daily rotation. */
    public function refresh(): void
    {
        $this->gate();
        $in = $this->input();
        $this->checkToken($in);

        $client = new LicenseClient();
        if (!$client->isActivated()) {
            $this->json(['success' => false, 'message' => 'This installation is not activated yet.'], 400);
        }
        $ok = $client->renew();
        $status = $client->status();
        $this->json([
            'success' => $ok,
            'message' => $ok
                ? 'Licence refreshed — valid for another ' . LicenseProtocol::human($status['expiresIn'] ?? 0) . '.'
                : ($status['lastError'] ?: 'Could not reach the licence server. Your licence keeps working until the grace period runs out.'),
            'status'  => $status,
        ]);
    }

    /** Release this seat so it can be used on another server. */
    public function deactivate(): void
    {
        $this->gate();
        $in = $this->input();
        $this->checkToken($in);

        try {
            (new LicenseClient())->deactivate();
        } catch (LicenseException $e) {
            $this->json(['success' => false, 'message' => $e->getMessage()], 400);
        }
        $this->json([
            'success' => true,
            'message' => 'Seat released. This installation is no longer activated.',
            'status'  => (new LicenseClient())->status(),
        ]);
    }
}
