<?php
/**
 * NIS2 Agile - Webhook Controller
 *
 * CRUD per API Keys e Webhook Subscriptions.
 * Gestione completa dal pannello Settings.
 *
 * Endpoint:
 *   --- API KEYS ---
 *   GET    /api/webhooks/api-keys          → lista API keys org
 *   POST   /api/webhooks/api-keys          → crea nuova API key
 *   DELETE /api/webhooks/api-keys/{id}     → revoca API key
 *
 *   --- WEBHOOK SUBSCRIPTIONS ---
 *   GET    /api/webhooks/subscriptions           → lista subscriptions
 *   POST   /api/webhooks/subscriptions           → crea subscription
 *   PUT    /api/webhooks/subscriptions/{id}      → aggiorna subscription
 *   DELETE /api/webhooks/subscriptions/{id}      → elimina subscription
 *   POST   /api/webhooks/subscriptions/{id}/test → invia ping di test
 *
 *   --- DELIVERIES ---
 *   GET    /api/webhooks/deliveries              → log delivery ultimi 100
 *   POST   /api/webhooks/retry                   → processa retry pendenti
 */

require_once __DIR__ . '/BaseController.php';
require_once APP_PATH . '/services/WebhookService.php';

class WebhookController extends BaseController
{
    // Scopes disponibili
    private const AVAILABLE_SCOPES = [
        'read:all'          => 'Accesso completo in lettura a tutti i dati',
        'read:compliance'   => 'Compliance score e controlli Art.21',
        'read:risks'        => 'Risk register e matrice rischi',
        'read:incidents'    => 'Incidenti e timeline Art.23',
        'read:assets'       => 'Inventario asset critici',
        'read:supply_chain' => 'Supply chain e rischio fornitori',
        'read:policies'     => 'Policy approvate',
    ];

    // ══════════════════════════════════════════════════════════════════════
    // API KEYS
    // ══════════════════════════════════════════════════════════════════════

    /**
     * GET /api/webhooks/api-keys
     */
    public function listApiKeys(): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

        $keys = Database::fetchAll(
            'SELECT ak.id, ak.name, ak.key_prefix, ak.scopes, ak.last_used_at,
                    ak.expires_at, ak.is_active, ak.created_at,
                    u.full_name as created_by_name
             FROM api_keys ak
             LEFT JOIN users u ON u.id = ak.created_by
             WHERE ak.organization_id = ?
             ORDER BY ak.created_at DESC',
            [$this->getCurrentOrgId()]
        );

        // Decodifica scopes
        foreach ($keys as &$key) {
            $key['scopes'] = json_decode($key['scopes'], true) ?? [];
        }
        unset($key);

        $this->jsonSuccess([
            'api_keys'         => $keys,
            'available_scopes' => self::AVAILABLE_SCOPES,
        ]);
    }

    /**
     * POST /api/webhooks/api-keys
     * Crea nuova API key. Restituisce la chiave completa UNA SOLA VOLTA.
     */
    public function createApiKey(): void
    {
        $this->requireOrgRole(['org_admin']);
        $this->validateRequired(['name', 'scopes']);

        $name   = trim($this->getParam('name'));
        $scopes = $this->getParam('scopes');
        if (is_string($scopes)) {
            $scopes = json_decode($scopes, true) ?? [];
        }

        // Valida scopes
        foreach ($scopes as $scope) {
            if (!array_key_exists($scope, self::AVAILABLE_SCOPES)) {
                $this->jsonError("Scope non valido: {$scope}", 400, 'INVALID_SCOPE');
            }
        }

        if (empty($scopes)) {
            $this->jsonError('Almeno uno scope è richiesto', 400, 'EMPTY_SCOPES');
        }

        // Genera chiave: nis2_ + 32 caratteri random
        $rawKey    = 'nis2_' . bin2hex(random_bytes(16));
        $prefix    = substr($rawKey, 0, 12);    // "nis2_xxxxxxx" (visibile)
        $keyHash   = hash('sha256', $rawKey);

        $expiresAt = $this->getParam('expires_at');
        $id = Database::insert('api_keys', [
            'organization_id' => $this->getCurrentOrgId(),
            'created_by'      => $this->getCurrentUserId(),
            'name'            => $name,
            'key_prefix'      => $prefix,
            'key_hash'        => $keyHash,
            'scopes'          => json_encode($scopes),
            'expires_at'      => $expiresAt ?: null,
            'is_active'       => 1,
        ]);

        $this->logAudit('api_key_created', 'api_key', $id, ['name' => $name, 'scopes' => $scopes]);

        $this->jsonSuccess([
            'id'          => $id,
            'name'        => $name,
            'key'         => $rawKey,         // ATTENZIONE: solo al momento della creazione!
            'key_prefix'  => $prefix,
            'scopes'      => $scopes,
            'expires_at'  => $expiresAt ?: null,
            'created_at'  => date('c'),
            'warning'     => 'Salva questa chiave in modo sicuro. Non sarà più visibile.',
        ], 'API Key creata con successo', 201);
    }

    /**
     * DELETE /api/webhooks/api-keys/{id}
     */
    public function deleteApiKey(int $id): void
    {
        $this->requireOrgRole(['org_admin']);

        $key = Database::fetchOne(
            'SELECT * FROM api_keys WHERE id = ? AND organization_id = ?',
            [$id, $this->getCurrentOrgId()]
        );

        if (!$key) {
            $this->jsonError('API Key non trovata', 404, 'NOT_FOUND');
        }

        Database::query(
            'UPDATE api_keys SET is_active = 0, updated_at = NOW() WHERE id = ?',
            [$id]
        );

        $this->logAudit('api_key_revoked', 'api_key', $id, ['name' => $key['name']]);
        $this->jsonSuccess(null, 'API Key revocata');
    }

    // ══════════════════════════════════════════════════════════════════════
    // WEBHOOK SUBSCRIPTIONS
    // ══════════════════════════════════════════════════════════════════════

    /**
     * GET /api/webhooks/subscriptions
     */
    public function listSubscriptions(): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

        $subs = Database::fetchAll(
            'SELECT ws.*, u.full_name as created_by_name,
                    (SELECT COUNT(*) FROM webhook_deliveries wd WHERE wd.subscription_id = ws.id) as total_deliveries,
                    (SELECT COUNT(*) FROM webhook_deliveries wd WHERE wd.subscription_id = ws.id AND wd.status = "delivered") as success_deliveries,
                    (SELECT COUNT(*) FROM webhook_deliveries wd WHERE wd.subscription_id = ws.id AND wd.status = "failed") as failed_deliveries
             FROM webhook_subscriptions ws
             LEFT JOIN users u ON u.id = ws.created_by
             WHERE ws.organization_id = ?
             ORDER BY ws.created_at DESC',
            [$this->getCurrentOrgId()]
        );

        foreach ($subs as &$sub) {
            $sub['events']  = json_decode($sub['events'], true) ?? [];
            unset($sub['secret']); // non esporre il secret
        }
        unset($sub);

        $this->jsonSuccess([
            'subscriptions'   => $subs,
            'available_events' => $this->availableEvents(),
        ]);
    }

    /**
     * POST /api/webhooks/subscriptions
     */
    public function createSubscription(): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);
        $this->validateRequired(['name', 'url', 'events']);

        $name   = trim($this->getParam('name'));
        $url    = trim($this->getParam('url'));
        $events = $this->getParam('events');
        if (is_string($events)) {
            $events = json_decode($events, true) ?? [];
        }

        // Valida URL
        if (!filter_var($url, FILTER_VALIDATE_URL)) {
            $this->jsonError('URL non valido', 400, 'INVALID_URL');
        }
        if (!in_array(parse_url($url, PHP_URL_SCHEME), ['http', 'https'])) {
            $this->jsonError('URL deve essere http o https', 400, 'INVALID_URL_SCHEME');
        }

        // Valida eventi
        $validEvents = array_keys($this->availableEvents());
        foreach ($events as $evt) {
            if ($evt !== '*' && !in_array($evt, $validEvents)) {
                $this->jsonError("Evento non valido: {$evt}", 400, 'INVALID_EVENT');
            }
        }

        // Genera secret HMAC
        $secret = bin2hex(random_bytes(24));

        $id = Database::insert('webhook_subscriptions', [
            'organization_id' => $this->getCurrentOrgId(),
            'created_by'      => $this->getCurrentUserId(),
            'name'            => $name,
            'url'             => $url,
            'secret'          => $secret,
            'events'          => json_encode(array_values($events)),
            'is_active'       => 1,
        ]);

        $this->logAudit('webhook_created', 'webhook_subscription', $id, ['name' => $name, 'url' => $url]);

        $this->jsonSuccess([
            'id'      => $id,
            'name'    => $name,
            'url'     => $url,
            'secret'  => $secret,    // Solo al momento della creazione!
            'events'  => $events,
            'warning' => 'Salva il secret. Sarà usato per verificare la firma X-NIS2-Signature.',
        ], 'Webhook creato', 201);
    }

    /**
     * PUT /api/webhooks/subscriptions/{id}
     */
    public function updateSubscription(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

        $sub = Database::fetchOne(
            'SELECT * FROM webhook_subscriptions WHERE id = ? AND organization_id = ?',
            [$id, $this->getCurrentOrgId()]
        );
        if (!$sub) {
            $this->jsonError('Webhook non trovato', 404, 'NOT_FOUND');
        }

        $updates = [];
        if ($this->hasParam('name'))      $updates['name']      = trim($this->getParam('name'));
        if ($this->hasParam('is_active')) $updates['is_active'] = (int)$this->getParam('is_active');
        if ($this->hasParam('events')) {
            $events = $this->getParam('events');
            if (is_string($events)) $events = json_decode($events, true) ?? [];
            $updates['events'] = json_encode(array_values($events));
        }

        if (!empty($updates)) {
            $updates['updated_at'] = date('Y-m-d H:i:s');
            $setClauses = implode(', ', array_map(fn($k) => "{$k} = ?", array_keys($updates)));
            Database::query(
                "UPDATE webhook_subscriptions SET {$setClauses} WHERE id = ?",
                array_merge(array_values($updates), [$id])
            );
        }

        $this->jsonSuccess(null, 'Webhook aggiornato');
    }

    /**
     * DELETE /api/webhooks/subscriptions/{id}
     */
    public function deleteSubscription(int $id): void
    {
        $this->requireOrgRole(['org_admin']);

        $sub = Database::fetchOne(
            'SELECT * FROM webhook_subscriptions WHERE id = ? AND organization_id = ?',
            [$id, $this->getCurrentOrgId()]
        );
        if (!$sub) {
            $this->jsonError('Webhook non trovato', 404, 'NOT_FOUND');
        }

        Database::query('DELETE FROM webhook_subscriptions WHERE id = ?', [$id]);
        $this->logAudit('webhook_deleted', 'webhook_subscription', $id, ['name' => $sub['name']]);
        $this->jsonSuccess(null, 'Webhook eliminato');
    }

    /**
     * POST /api/webhooks/subscriptions/{id}/test
     * Invia un evento ping di test al webhook.
     */
    public function testSubscription(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

        $sub = Database::fetchOne(
            'SELECT * FROM webhook_subscriptions WHERE id = ? AND organization_id = ?',
            [$id, $this->getCurrentOrgId()]
        );
        if (!$sub) {
            $this->jsonError('Webhook non trovato', 404, 'NOT_FOUND');
        }

        $webhookService = new WebhookService();
        $testPayload = [
            'message' => 'Questo è un evento di test da NIS2 Agile.',
            'timestamp' => date('c'),
        ];

        $webhookService->dispatch($this->getCurrentOrgId(), 'webhook.test', $testPayload);

        $this->jsonSuccess([
            'subscription_id' => $id,
            'url'             => $sub['url'],
            'event'           => 'webhook.test',
        ], 'Ping di test inviato. Controlla i delivery log per il risultato.');
    }

    // ══════════════════════════════════════════════════════════════════════
    // DELIVERIES
    // ══════════════════════════════════════════════════════════════════════

    /**
     * GET /api/webhooks/deliveries
     * Ultimi 100 delivery log per l'organizzazione.
     */
    public function listDeliveries(): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

        $subFilter = '';
        $params    = [$this->getCurrentOrgId()];

        if ($this->hasParam('subscription_id')) {
            $subFilter = ' AND wd.subscription_id = ?';
            $params[]  = (int)$this->getParam('subscription_id');
        }

        $deliveries = Database::fetchAll(
            "SELECT wd.id, wd.event_type, wd.event_id, wd.status, wd.http_status,
                    wd.attempt, wd.delivered_at, wd.next_retry_at, wd.created_at,
                    ws.name as subscription_name, ws.url
             FROM webhook_deliveries wd
             JOIN webhook_subscriptions ws ON ws.id = wd.subscription_id
             WHERE wd.organization_id = ? {$subFilter}
             ORDER BY wd.created_at DESC
             LIMIT 100",
            $params
        );

        $this->jsonSuccess(['deliveries' => $deliveries]);
    }

    /**
     * POST /api/webhooks/retry
     * Processa retry pendenti (anche richiamabile da cron).
     */
    public function processRetry(): void
    {
        $this->requireOrgRole(['org_admin']);
        $webhookService = new WebhookService();
        $count = $webhookService->processRetries();
        $this->jsonSuccess(['processed' => $count], "Processati {$count} retry");
    }

    // ── Utility ───────────────────────────────────────────────────────────

    private function availableEvents(): array
    {
        return [
            'incident.created'          => 'Nuovo incidente creato',
            'incident.updated'          => 'Incidente aggiornato',
            'incident.significant'      => 'Incidente significativo (Art.23 attivato)',
            'incident.deadline_warning' => 'Scadenza Art.23 imminente (24h/72h)',
            'risk.high_created'         => 'Nuovo rischio HIGH o CRITICAL',
            'risk.updated'              => 'Rischio aggiornato',
            'compliance.score_changed'  => 'Variazione compliance score >5%',
            'policy.approved'           => 'Policy approvata',
            'policy.created'            => 'Nuova policy creata',
            'supplier.risk_flagged'     => 'Fornitore con rischio HIGH/CRITICAL',
            'assessment.completed'      => 'Gap assessment completato',
            'whistleblowing.received'   => 'Nuova segnalazione ricevuta',
            'normative.update'          => 'Aggiornamento normativo NIS2/ACN',
            'webhook.test'              => 'Evento di test',
            '*'                         => 'Tutti gli eventi (wildcard)',
        ];
    }
}
