<?php
/**
 * NIS2 Agile - Whistleblowing Controller (Art.32 NIS2)
 *
 * Canale segnalazioni anomalie di sicurezza, con anonimato garantito.
 * Art. 32 D.Lgs. 138/2024: le entità NIS2 devono predisporre canali
 * interni per la segnalazione di violazioni alla sicurezza informatica.
 *
 * Endpoint:
 *   POST   /api/whistleblowing/submit         → invia segnalazione (anonima o firmata)
 *   GET    /api/whistleblowing/list            → lista segnalazioni (CISO/admin)
 *   GET    /api/whistleblowing/{id}            → dettaglio segnalazione
 *   PUT    /api/whistleblowing/{id}            → aggiorna status/priorità/note
 *   POST   /api/whistleblowing/{id}/assign     → assegna a utente
 *   POST   /api/whistleblowing/{id}/close      → chiudi segnalazione
 *   GET    /api/whistleblowing/stats           → statistiche per dashboard
 *   GET    /api/whistleblowing/track-anonymous → tracking anonimo (via token)
 */

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

class WhistleblowingController extends BaseController
{
    // ══════════════════════════════════════════════════════════════════════
    // SUBMIT (pubblica — anche per utenti non autenticati)
    // ══════════════════════════════════════════════════════════════════════

    /**
     * POST /api/whistleblowing/submit
     * Invia segnalazione. Supporta anonima (no auth) o firmata (auth opzionale).
     */
    public function submit(): void
    {
        // Nota: non richiede auth — supporta segnalazioni anonime
        $this->validateRequired(['category', 'title', 'description']);

        $orgId = (int)($this->getParam('organization_id') ?: $this->getCurrentOrgId());
        if (!$orgId) {
            $this->jsonError('organization_id obbligatorio per segnalazioni anonime', 400, 'ORG_REQUIRED');
        }

        $category    = $this->getParam('category');
        $title       = trim($this->getParam('title'));
        $description = trim($this->getParam('description'));
        $priority    = $this->getParam('priority', 'medium');
        $contactEmail = $this->getParam('contact_email');
        $nisArticle  = $this->getParam('nis2_article');

        // Valida categoria
        $validCategories = ['security_incident','policy_violation','unauthorized_access','data_breach',
            'supply_chain_risk','corruption','fraud','nis2_non_compliance','other'];
        if (!in_array($category, $validCategories)) {
            $this->jsonError("Categoria non valida: {$category}", 400, 'INVALID_CATEGORY');
        }

        // Determina se anonima
        $userId = null;
        $isAnonymous = 1;
        try {
            $userId = $this->getCurrentUserId();
            $isAnonymous = $this->getParam('is_anonymous', 0) ? 1 : 0;
        } catch (Throwable) {
            // Nessuna auth → forza anonima
            $isAnonymous = 1;
        }

        // Token anonimo per tracking
        $anonymousToken = $isAnonymous ? bin2hex(random_bytes(24)) : null;

        $code = $this->generateCode('WB');
        $reportId = Database::insert('whistleblowing_reports', [
            'organization_id' => $orgId,
            'report_code'     => $code,
            'is_anonymous'    => $isAnonymous,
            'submitted_by'    => $isAnonymous ? null : $userId,
            'anonymous_token' => $anonymousToken,
            'contact_email'   => $contactEmail ?: null,
            'category'        => $category,
            'title'           => $title,
            'description'     => $description,
            'nis2_article'    => $nisArticle ?: null,
            'priority'        => in_array($priority, ['critical','high','medium','low']) ? $priority : 'medium',
            'status'          => 'received',
        ]);

        // Prima voce timeline
        Database::insert('whistleblowing_timeline', [
            'report_id'               => $reportId,
            'event_type'              => 'received',
            'description'             => 'Segnalazione ricevuta tramite canale interno.',
            'is_visible_to_reporter'  => 1,
        ]);

        // Dispatch webhook
        try {
            (new WebhookService())->dispatch($orgId, 'whistleblowing.received', [
                'id'        => $reportId,
                'code'      => $code,
                'category'  => $category,
                'priority'  => $priority,
                'anonymous' => (bool)$isAnonymous,
            ]);
        } catch (Throwable $e) {
            error_log('[WEBHOOK] dispatch error: ' . $e->getMessage());
        }

        $this->jsonSuccess([
            'id'              => $reportId,
            'report_code'     => $code,
            'anonymous_token' => $anonymousToken,  // Usabile per tracking se anonima
            'note'            => $anonymousToken
                ? 'Conserva questo token per verificare lo stato della segnalazione: /api/whistleblowing/track-anonymous?token=' . $anonymousToken
                : null,
        ], 'Segnalazione ricevuta', 201);
    }

    // ══════════════════════════════════════════════════════════════════════
    // LIST (solo CISO/admin)
    // ══════════════════════════════════════════════════════════════════════

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

        $conditions = ['wr.organization_id = ?'];
        $params = [$this->getCurrentOrgId()];

        if ($this->hasParam('status')) {
            $conditions[] = 'wr.status = ?';
            $params[] = $this->getParam('status');
        }
        if ($this->hasParam('priority')) {
            $conditions[] = 'wr.priority = ?';
            $params[] = $this->getParam('priority');
        }
        if ($this->hasParam('category')) {
            $conditions[] = 'wr.category = ?';
            $params[] = $this->getParam('category');
        }

        $where = implode(' AND ', $conditions);
        $reports = Database::fetchAll(
            "SELECT wr.id, wr.report_code, wr.category, wr.title, wr.priority, wr.status,
                    wr.is_anonymous, wr.created_at, wr.closed_at,
                    u.full_name as assigned_to_name
             FROM whistleblowing_reports wr
             LEFT JOIN users u ON u.id = wr.assigned_to
             WHERE {$where}
             ORDER BY
                CASE wr.priority WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 ELSE 4 END,
                wr.created_at DESC",
            $params
        );

        $this->jsonSuccess(['reports' => $reports, 'total' => count($reports)]);
    }

    /**
     * GET /api/whistleblowing/{id}
     */
    public function get(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

        $report = Database::fetchOne(
            'SELECT wr.*, u1.full_name as assigned_to_name, u2.full_name as submitted_by_name
             FROM whistleblowing_reports wr
             LEFT JOIN users u1 ON u1.id = wr.assigned_to
             LEFT JOIN users u2 ON u2.id = wr.submitted_by
             WHERE wr.id = ? AND wr.organization_id = ?',
            [$id, $this->getCurrentOrgId()]
        );

        if (!$report) {
            $this->jsonError('Segnalazione non trovata', 404, 'NOT_FOUND');
        }

        // Non esporre token e contact_email (privacy)
        unset($report['anonymous_token']);
        if ($report['is_anonymous']) unset($report['contact_email']);

        $report['timeline'] = Database::fetchAll(
            'SELECT wt.*, u.full_name as created_by_name
             FROM whistleblowing_timeline wt
             LEFT JOIN users u ON u.id = wt.created_by
             WHERE wt.report_id = ?
             ORDER BY wt.created_at ASC',
            [$id]
        );

        $this->jsonSuccess($report);
    }

    /**
     * PUT /api/whistleblowing/{id}
     * Aggiorna status, priorità, note di risoluzione.
     */
    public function update(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

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

        $updates = [];
        $oldStatus = $report['status'];

        foreach (['priority', 'resolution_notes', 'nis2_article'] as $field) {
            if ($this->hasParam($field)) $updates[$field] = $this->getParam($field);
        }

        if ($this->hasParam('status')) {
            $newStatus = $this->getParam('status');
            $validStatuses = ['received','under_review','investigating','resolved','closed','rejected'];
            if (!in_array($newStatus, $validStatuses)) {
                $this->jsonError("Status non valido: {$newStatus}", 400, 'INVALID_STATUS');
            }
            $updates['status'] = $newStatus;

            // Aggiungi voce timeline su cambio status
            if ($newStatus !== $oldStatus) {
                Database::insert('whistleblowing_timeline', [
                    'report_id'              => $id,
                    'event_type'             => 'status_change',
                    'description'            => "Status cambiato da '{$oldStatus}' a '{$newStatus}'.",
                    'new_status'             => $newStatus,
                    'created_by'             => $this->getCurrentUserId(),
                    'is_visible_to_reporter' => 1,
                ]);
            }
        }

        if (!empty($updates)) {
            Database::query(
                'UPDATE whistleblowing_reports SET ' .
                implode(', ', array_map(fn($k) => "{$k} = ?", array_keys($updates))) .
                ', updated_at = NOW() WHERE id = ?',
                array_merge(array_values($updates), [$id])
            );
            $this->logAudit('whistleblowing_updated', 'whistleblowing', $id, $updates);
        }

        $this->jsonSuccess(null, 'Segnalazione aggiornata');
    }

    /**
     * POST /api/whistleblowing/{id}/assign
     */
    public function assign(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);
        $this->validateRequired(['user_id']);

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

        $userId = (int)$this->getParam('user_id');
        Database::query(
            'UPDATE whistleblowing_reports SET assigned_to = ?, updated_at = NOW() WHERE id = ?',
            [$userId, $id]
        );

        $user = Database::fetchOne('SELECT full_name FROM users WHERE id = ?', [$userId]);
        Database::insert('whistleblowing_timeline', [
            'report_id'    => $id,
            'event_type'   => 'assigned',
            'description'  => "Segnalazione assegnata a " . ($user['full_name'] ?? "utente #{$userId}"),
            'created_by'   => $this->getCurrentUserId(),
        ]);

        $this->jsonSuccess(null, 'Segnalazione assegnata');
    }

    /**
     * POST /api/whistleblowing/{id}/close
     */
    public function close(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

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

        $resolution = trim($this->getParam('resolution_notes', ''));
        Database::query(
            'UPDATE whistleblowing_reports SET status = "closed", closed_at = NOW(),
             closed_by = ?, resolution_notes = ?, updated_at = NOW() WHERE id = ?',
            [$this->getCurrentUserId(), $resolution, $id]
        );

        Database::insert('whistleblowing_timeline', [
            'report_id'              => $id,
            'event_type'             => 'closed',
            'description'            => 'Segnalazione chiusa.' . ($resolution ? " Note: {$resolution}" : ''),
            'created_by'             => $this->getCurrentUserId(),
            'is_visible_to_reporter' => 1,
        ]);

        $this->logAudit('whistleblowing_closed', 'whistleblowing', $id, ['resolution' => $resolution]);
        $this->jsonSuccess(null, 'Segnalazione chiusa');
    }

    /**
     * GET /api/whistleblowing/stats
     */
    public function stats(): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);
        $orgId = $this->getCurrentOrgId();

        $stats = Database::fetchOne(
            'SELECT
                COUNT(*) as total,
                SUM(CASE WHEN status = "received" THEN 1 ELSE 0 END) as received,
                SUM(CASE WHEN status = "under_review" THEN 1 ELSE 0 END) as under_review,
                SUM(CASE WHEN status = "investigating" THEN 1 ELSE 0 END) as investigating,
                SUM(CASE WHEN status IN ("resolved","closed") THEN 1 ELSE 0 END) as closed,
                SUM(CASE WHEN priority = "critical" THEN 1 ELSE 0 END) as critical,
                SUM(CASE WHEN priority = "high" THEN 1 ELSE 0 END) as high,
                SUM(CASE WHEN is_anonymous = 1 THEN 1 ELSE 0 END) as anonymous_count
             FROM whistleblowing_reports
             WHERE organization_id = ?',
            [$orgId]
        );

        $byCategory = Database::fetchAll(
            'SELECT category, COUNT(*) as count
             FROM whistleblowing_reports
             WHERE organization_id = ?
             GROUP BY category
             ORDER BY count DESC',
            [$orgId]
        );

        $this->jsonSuccess(['stats' => $stats, 'by_category' => $byCategory]);
    }

    /**
     * GET /api/whistleblowing/track-anonymous
     * Permette a segnalante anonimo di verificare stato via token.
     */
    public function trackAnonymous(): void
    {
        $token = $this->getParam('token');
        if (!$token) { $this->jsonError('Token obbligatorio', 400, 'TOKEN_REQUIRED'); }

        $report = Database::fetchOne(
            'SELECT id, report_code, category, status, created_at
             FROM whistleblowing_reports
             WHERE anonymous_token = ?',
            [$token]
        );

        if (!$report) { $this->jsonError('Token non valido', 404, 'INVALID_TOKEN'); }

        // Solo eventi visibili al reporter
        $timeline = Database::fetchAll(
            'SELECT event_type, description, created_at
             FROM whistleblowing_timeline
             WHERE report_id = ? AND is_visible_to_reporter = 1
             ORDER BY created_at ASC',
            [$report['id']]
        );

        $this->jsonSuccess([
            'report_code' => $report['report_code'],
            'category'    => $report['category'],
            'status'      => $report['status'],
            'created_at'  => $report['created_at'],
            'timeline'    => $timeline,
        ]);
    }
}
