<?php
/**
 * NIS2 Agile - Asset Controller
 *
 * Inventario asset IT/OT, classificazione, dipendenze.
 */

require_once __DIR__ . '/BaseController.php';
require_once __DIR__ . '/../services/AssetScoringService.php';

class AssetController extends BaseController
{
    public function list(): void
    {
        $this->requireOrgAccess();
        $pagination = $this->getPagination();

        $where = 'organization_id = ?';
        $params = [$this->getCurrentOrgId()];

        if ($this->hasParam('asset_type')) {
            $where .= ' AND asset_type = ?';
            $params[] = $this->getParam('asset_type');
        }
        if ($this->hasParam('criticality')) {
            $where .= ' AND criticality = ?';
            $params[] = $this->getParam('criticality');
        }
        if ($this->hasParam('status')) {
            $where .= ' AND status = ?';
            $params[] = $this->getParam('status');
        }
        if ($this->hasParam('nis2_relevant')) {
            $where .= ' AND is_nis2_relevant = ?';
            $params[] = $this->getParam('nis2_relevant') ? 1 : 0;
        }

        $total = Database::count('assets', $where, $params);
        $assets = Database::fetchAll(
            "SELECT a.*, u.full_name as owner_name
             FROM assets a
             LEFT JOIN users u ON u.id = a.owner_user_id
             WHERE a.{$where}
             ORDER BY a.criticality DESC, a.name
             LIMIT {$pagination['per_page']} OFFSET {$pagination['offset']}",
            $params
        );

        $this->jsonPaginated($assets, $total, $pagination['page'], $pagination['per_page']);
    }

    public function create(): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);
        $this->validateRequired(['name', 'asset_type']);

        $assetId = Database::insert('assets', [
            'organization_id' => $this->getCurrentOrgId(),
            'name'            => trim($this->getParam('name')),
            'asset_type'      => $this->getParam('asset_type'),
            'category'        => $this->getParam('category'),
            'description'     => $this->getParam('description'),
            'criticality'     => $this->getParam('criticality', 'medium'),
            'owner_user_id'   => $this->getParam('owner_user_id'),
            'location'        => $this->getParam('location'),
            'ip_address'      => $this->getParam('ip_address'),
            'vendor'          => $this->getParam('vendor'),
            'version'         => $this->getParam('version'),
            'serial_number'   => $this->getParam('serial_number'),
            'purchase_date'   => $this->getParam('purchase_date'),
            'warranty_expiry' => $this->getParam('warranty_expiry'),
            'dependencies'    => $this->getParam('dependencies') ? json_encode($this->getParam('dependencies')) : null,
        ]);

        $this->logAudit('asset_created', 'asset', $assetId);
        $this->jsonSuccess(['id' => $assetId], 'Asset registrato', 201);
    }

    /**
     * POST /api/assets/import  (JWT, org_admin/compliance_manager)
     * Import bulk asset da CMDB/cloud/CSV con scoring automatico GV.OC-04.
     * Body: { "source":"cmdb|aws|azure|csv|manual", "assets":[ {...}, ... ] }
     */
    public function import(): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);
        $body   = $this->getJsonBody();
        $source = strtolower((string) ($body['source'] ?? 'csv'));
        $items  = $body['assets'] ?? null;
        if (!is_array($items) || !$items) {
            $this->jsonError('Campo "assets" (array) obbligatorio', 422, 'VALIDATION');
        }
        $result = self::bulkUpsert($this->getCurrentOrgId(), $items, $source, $this->getCurrentUserId());
        $this->logAudit('assets_imported', 'asset', null, [
            'source' => $source, 'imported' => $result['imported'], 'updated' => $result['updated'],
        ]);
        $this->jsonSuccess($result, 'Import completato', 201);
    }

    /**
     * Upsert bulk + scoring GV.OC-04. Condiviso fra import UI (JWT) e
     * ingestion connettori (API key, ServicesController). Riceve orgId esplicito.
     *
     * @return array{imported:int,updated:int,skipped:int,relevant:int,total:int,results:array}
     */
    public static function bulkUpsert(int $orgId, array $items, string $source, ?int $userId): array
    {
        $validType = ['hardware', 'software', 'network', 'data', 'service', 'personnel', 'facility'];
        $imported = 0; $updated = 0; $skipped = 0; $relevant = 0; $results = [];

        if (count($items) > 1000) {
            $items = array_slice($items, 0, 1000);
        }

        foreach ($items as $i => $a) {
            if (!is_array($a)) { $skipped++; $results[] = ['index' => $i, 'ok' => false, 'error' => 'not_an_object']; continue; }
            $name = trim((string) ($a['name'] ?? ''));
            if ($name === '') { $skipped++; $results[] = ['index' => $i, 'ok' => false, 'error' => 'name mancante']; continue; }

            $type = strtolower((string) ($a['asset_type'] ?? 'service'));
            if (!in_array($type, $validType, true)) $type = 'service';

            // Scoring automatico GV.OC-04 da euristica sui campi CMDB
            $criteria = AssetScoringService::inferCriteria($a);
            $sc       = AssetScoringService::calculate($criteria);

            $extRef = isset($a['external_ref']) ? substr(trim((string) $a['external_ref']), 0, 190) : null;

            $row = [
                'organization_id'       => $orgId,
                'name'                  => $name,
                'asset_type'            => $type,
                'category'              => $a['category'] ?? null,
                'description'           => $a['description'] ?? null,
                'criticality'           => $sc['criticality'],
                'location'              => $a['location'] ?? null,
                'ip_address'            => $a['ip_address'] ?? null,
                'vendor'                => $a['vendor'] ?? null,
                'discovery_source'      => substr($source, 0, 40),
                'external_ref'          => $extRef,
                'relevance_score'       => $sc['score'],
                'relevance_criteria'    => json_encode($criteria, JSON_UNESCAPED_UNICODE),
                'relevance_class'       => $sc['class'],
                'is_nis2_relevant'      => $sc['is_relevant'] ? 1 : 0,
                'relevance_assessed_at' => date('Y-m-d H:i:s'),
                'relevance_assessed_by' => $userId,
            ];

            try {
                $existing = $extRef !== null
                    ? Database::fetchOne('SELECT id FROM assets WHERE organization_id = ? AND external_ref = ?', [$orgId, $extRef])
                    : null;
                if ($existing) {
                    $sets = []; $vals = [];
                    foreach ($row as $k => $v) { if ($k === 'organization_id') continue; $sets[] = "$k = ?"; $vals[] = $v; }
                    $vals[] = $existing['id'];
                    Database::query('UPDATE assets SET ' . implode(', ', $sets) . ' WHERE id = ?', $vals);
                    $assetId = (int) $existing['id']; $updated++;
                } else {
                    $assetId = Database::insert('assets', $row); $imported++;
                }
            } catch (Throwable $e) {
                $skipped++; $results[] = ['index' => $i, 'ok' => false, 'error' => 'db_error'];
                error_log('[ASSET_IMPORT] ' . $e->getMessage());
                continue;
            }

            if ($sc['is_relevant']) $relevant++;
            $results[] = [
                'index' => $i, 'ok' => true, 'id' => $assetId, 'name' => $name,
                'relevance_score' => $sc['score'], 'relevance_class' => $sc['class'],
                'nis2_relevant' => $sc['is_relevant'],
            ];
        }

        return [
            'imported' => $imported, 'updated' => $updated, 'skipped' => $skipped,
            'relevant' => $relevant, 'total' => count($items), 'results' => $results,
        ];
    }

    public function get(int $id): void
    {
        $this->requireOrgAccess();

        $asset = Database::fetchOne(
            'SELECT a.*, u.full_name as owner_name
             FROM assets a LEFT JOIN users u ON u.id = a.owner_user_id
             WHERE a.id = ? AND a.organization_id = ?',
            [$id, $this->getCurrentOrgId()]
        );

        if (!$asset) {
            $this->jsonError('Asset non trovato', 404, 'ASSET_NOT_FOUND');
        }

        $this->jsonSuccess($asset);
    }

    public function update(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

        $updates = [];
        $fields = ['name', 'asset_type', 'category', 'description', 'criticality',
                    'owner_user_id', 'location', 'ip_address', 'vendor', 'version',
                    'serial_number', 'purchase_date', 'warranty_expiry', 'status'];

        foreach ($fields as $field) {
            if ($this->hasParam($field)) {
                $updates[$field] = $this->getParam($field);
            }
        }

        if ($this->hasParam('dependencies')) {
            $updates['dependencies'] = json_encode($this->getParam('dependencies'));
        }

        if (!empty($updates)) {
            Database::update('assets', $updates, 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
            $this->logAudit('asset_updated', 'asset', $id, $updates);
        }

        $this->jsonSuccess($updates, 'Asset aggiornato');
    }

    public function delete(int $id): void
    {
        $this->requireOrgRole(['org_admin']);
        $deleted = Database::delete('assets', 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
        if ($deleted === 0) {
            $this->jsonError('Asset non trovato', 404, 'ASSET_NOT_FOUND');
        }
        $this->logAudit('asset_deleted', 'asset', $id);
        $this->jsonSuccess(null, 'Asset eliminato');
    }

    public function dependencyMap(): void
    {
        $this->requireOrgAccess();

        $assets = Database::fetchAll(
            'SELECT id, name, asset_type, criticality, dependencies
             FROM assets
             WHERE organization_id = ? AND status = "active"',
            [$this->getCurrentOrgId()]
        );

        $nodes = [];
        $edges = [];

        foreach ($assets as $asset) {
            $nodes[] = [
                'id'          => $asset['id'],
                'label'       => $asset['name'],
                'type'        => $asset['asset_type'],
                'criticality' => $asset['criticality'],
            ];

            $deps = json_decode($asset['dependencies'] ?? '[]', true) ?: [];
            foreach ($deps as $depId) {
                $edges[] = ['from' => $asset['id'], 'to' => (int) $depId];
            }
        }

        $this->jsonSuccess(['nodes' => $nodes, 'edges' => $edges]);
    }

    /**
     * GET /api/assets/scoringGrid
     * Ritorna la griglia ufficiale di valutazione rilevanza NIS2 (GV.OC-04)
     * per costruire la UI di scoring lato client.
     */
    public function scoringGrid(): void
    {
        $this->requireOrgAccess();
        $this->jsonSuccess([
            'grid'        => AssetScoringService::GRID,
            'threshold'   => AssetScoringService::RELEVANCE_THRESHOLD,
            'classes'     => [
                ['key' => 'critico',      'min' => 80, 'max' => 100, 'label' => 'Critico - Priorita Massima'],
                ['key' => 'alto',         'min' => 60, 'max' => 79,  'label' => 'Alto - Priorita Alta'],
                ['key' => 'medio',        'min' => 40, 'max' => 59,  'label' => 'Medio - Rilevante'],
                ['key' => 'basso',        'min' => 20, 'max' => 39,  'label' => 'Basso - Monitoraggio'],
                ['key' => 'trascurabile', 'min' => 0,  'max' => 19,  'label' => 'Trascurabile'],
            ],
        ]);
    }

    /**
     * POST /api/assets/{id}/score
     * Calcola e salva la rilevanza NIS2 dell'asset a partire dalle selezioni
     * sui 6 criteri. Body: { criteria: { c1_operational_criticality: 'critical', ... } }
     */
    public function score(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

        $asset = Database::fetchOne(
            'SELECT id FROM assets WHERE id = ? AND organization_id = ?',
            [$id, $this->getCurrentOrgId()]
        );
        if (!$asset) {
            $this->jsonError('Asset non trovato', 404, 'ASSET_NOT_FOUND');
        }

        $criteria = $this->getParam('criteria');
        if (!is_array($criteria)) {
            $this->jsonError('Campo "criteria" mancante o non valido', 422, 'INVALID_CRITERIA');
        }

        try {
            $result = AssetScoringService::calculate($criteria);
        } catch (InvalidArgumentException $e) {
            $this->jsonError($e->getMessage(), 422, 'INVALID_CRITERIA');
            return;
        }

        Database::update('assets', [
            'relevance_score'       => $result['score'],
            'relevance_criteria'    => json_encode($criteria, JSON_UNESCAPED_UNICODE),
            'relevance_class'       => $result['class'],
            'is_nis2_relevant'      => $result['is_relevant'] ? 1 : 0,
            'criticality'           => $result['criticality'],
            'relevance_assessed_at' => date('Y-m-d H:i:s'),
            'relevance_assessed_by' => $this->getCurrentUserId(),
        ], 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);

        $this->logAudit('asset_scored', 'asset', $id, [
            'score' => $result['score'],
            'class' => $result['class'],
        ]);

        $this->jsonSuccess([
            'score'            => $result['score'],
            'class'            => $result['class'],
            'is_nis2_relevant' => $result['is_relevant'],
            'breakdown'        => $result['breakdown'],
            'required_measures'=> AssetScoringService::requiredMeasures($result['class']),
        ], 'Rilevanza NIS2 calcolata');
    }

    /**
     * GET /api/assets/relevantSystems
     * Elenco dei sistemi classificati rilevanti NIS2 (score >= 40), ordinati
     * per punteggio. Alimenta il registro formale "Sistemi Rilevanti" (GV.OC-04).
     */
    public function relevantSystems(): void
    {
        $this->requireOrgAccess();

        $rows = Database::fetchAll(
            "SELECT a.id, a.name, a.asset_type, a.category, a.ip_address, a.location,
                    a.relevance_score, a.relevance_class, a.relevance_criteria,
                    a.relevance_assessed_at, u.full_name AS owner_name
             FROM assets a
             LEFT JOIN users u ON u.id = a.owner_user_id
             WHERE a.organization_id = ? AND a.is_nis2_relevant = 1
             ORDER BY a.relevance_score DESC, a.name",
            [$this->getCurrentOrgId()]
        );

        $stats = ['critico' => 0, 'alto' => 0, 'medio' => 0];
        foreach ($rows as &$r) {
            $r['relevance_criteria'] = json_decode($r['relevance_criteria'] ?? 'null', true);
            $r['required_measures']  = AssetScoringService::requiredMeasures($r['relevance_class'] ?? '');
            if (isset($stats[$r['relevance_class']])) {
                $stats[$r['relevance_class']]++;
            }
        }
        unset($r);

        $this->jsonSuccess([
            'systems'    => $rows,
            'count'      => count($rows),
            'by_class'   => $stats,
            'threshold'  => AssetScoringService::RELEVANCE_THRESHOLD,
        ]);
    }
}
