<?php
/**
 * NIS2 Agile - Organization Controller
 *
 * Gestione organizzazioni multi-tenant, membri, classificazione NIS2.
 */

require_once __DIR__ . '/BaseController.php';

class OrganizationController extends BaseController
{
    /**
     * POST /api/organizations/create
     */
    public function create(): void
    {
        $this->requireAuth();
        $this->validateRequired(['name', 'sector']);

        $name = trim($this->getParam('name'));
        $sector = $this->getParam('sector');
        $vatNumber = $this->getParam('vat_number');
        $fiscalCode = $this->getParam('fiscal_code');

        // Valida P.IVA se fornita
        if ($vatNumber && !$this->validateVAT($vatNumber)) {
            $this->jsonError('Partita IVA non valida', 400, 'INVALID_VAT');
        }

        Database::beginTransaction();
        try {
            // Crea organizzazione
            $orgId = Database::insert('organizations', [
                'name'                => $name,
                'vat_number'          => $vatNumber,
                'fiscal_code'         => $fiscalCode,
                'sector'              => $sector,
                'employee_count'      => $this->getParam('employee_count'),
                'annual_turnover_eur' => $this->getParam('annual_turnover_eur'),
                'country'             => $this->getParam('country', 'IT'),
                'city'                => $this->getParam('city'),
                'address'             => $this->getParam('address'),
                'website'             => $this->getParam('website'),
                'contact_email'       => $this->getParam('contact_email'),
                'contact_phone'       => $this->getParam('contact_phone'),
            ]);

            // Auto-classifica entità NIS2
            $entityType = $this->classifyNis2Entity(
                $sector,
                (int) $this->getParam('employee_count', 0),
                (float) $this->getParam('annual_turnover_eur', 0)
            );

            Database::update('organizations', [
                'entity_type' => $entityType,
            ], 'id = ?', [$orgId]);

            // Aggiungi creatore come org_admin
            Database::insert('user_organizations', [
                'user_id'         => $this->getCurrentUserId(),
                'organization_id' => $orgId,
                'role'            => 'org_admin',
                'is_primary'      => 1,
            ]);

            // Inizializza controlli di compliance NIS2
            $this->initializeComplianceControls($orgId);

            Database::commit();

            $this->currentOrgId = $orgId;
            $this->logAudit('organization_created', 'organization', $orgId, [
                'name' => $name, 'sector' => $sector, 'entity_type' => $entityType
            ]);

            $this->jsonSuccess([
                'id'          => $orgId,
                'name'        => $name,
                'sector'      => $sector,
                'entity_type' => $entityType,
            ], 'Organizzazione creata', 201);

        } catch (Throwable $e) {
            Database::rollback();
            throw $e;
        }
    }

    /**
     * GET /api/organizations/current
     */
    public function getCurrent(): void
    {
        $this->requireOrgAccess();

        $org = Database::fetchOne(
            'SELECT * FROM organizations WHERE id = ?',
            [$this->getCurrentOrgId()]
        );

        if (!$org) {
            $this->jsonError('Organizzazione non trovata', 404, 'ORG_NOT_FOUND');
        }

        // Conta membri
        $memberCount = Database::count(
            'user_organizations',
            'organization_id = ?',
            [$this->getCurrentOrgId()]
        );

        $org['member_count'] = $memberCount;
        $org['current_user_role'] = $this->currentOrgRole;

        $this->jsonSuccess($org);
    }

    /**
     * GET /api/organizations/list
     */
    public function list(): void
    {
        $this->requireAuth();

        if ($this->currentUser['role'] === 'super_admin') {
            $orgs = Database::fetchAll('SELECT * FROM organizations WHERE is_active = 1 ORDER BY name');
        } else {
            $orgs = Database::fetchAll(
                'SELECT o.*, uo.role as user_role, uo.is_primary
                 FROM organizations o
                 JOIN user_organizations uo ON uo.organization_id = o.id
                 WHERE uo.user_id = ? AND o.is_active = 1
                 ORDER BY uo.is_primary DESC, o.name',
                [$this->getCurrentUserId()]
            );
        }

        $this->jsonSuccess($orgs);
    }

    /**
     * PUT /api/organizations/{id}
     */
    public function update(int $id): void
    {
        $this->requireOrgRole(['org_admin']);

        if ($id !== $this->getCurrentOrgId()) {
            $this->jsonError('ID organizzazione non corrisponde', 400, 'ORG_MISMATCH');
        }

        $updates = [];
        $allowedFields = [
            'name', 'vat_number', 'fiscal_code', 'sector', 'employee_count',
            'annual_turnover_eur', 'country', 'city', 'address', 'website',
            'contact_email', 'contact_phone', 'voluntary_compliance',
        ];

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

        if (empty($updates)) {
            $this->jsonError('Nessun campo da aggiornare', 400, 'NO_UPDATES');
        }

        // Ri-classifica se cambiano settore, dipendenti o fatturato
        if (isset($updates['sector']) || isset($updates['employee_count']) || isset($updates['annual_turnover_eur'])) {
            $org = Database::fetchOne('SELECT * FROM organizations WHERE id = ?', [$id]);
            $newEntityType = $this->classifyNis2Entity(
                $updates['sector'] ?? $org['sector'],
                (int) ($updates['employee_count'] ?? $org['employee_count']),
                (float) ($updates['annual_turnover_eur'] ?? $org['annual_turnover_eur'])
            );
            $updates['entity_type'] = $newEntityType;
            // Reset voluntary compliance if no longer not_applicable
            if ($newEntityType !== 'not_applicable') {
                $updates['voluntary_compliance'] = 0;
            }
        }

        // Ensure voluntary_compliance is only set when entity_type is not_applicable
        if (isset($updates['voluntary_compliance'])) {
            $currentOrg = $org ?? Database::fetchOne('SELECT entity_type FROM organizations WHERE id = ?', [$id]);
            $currentType = $updates['entity_type'] ?? $currentOrg['entity_type'];
            if ($currentType !== 'not_applicable') {
                $updates['voluntary_compliance'] = 0;
            }
        }

        Database::update('organizations', $updates, 'id = ?', [$id]);

        $this->logAudit('organization_updated', 'organization', $id, $updates);

        $this->jsonSuccess($updates, 'Organizzazione aggiornata');
    }

    /**
     * GET /api/organizations/{id}/members
     */
    public function listMembers(int $id): void
    {
        $this->requireOrgAccess();

        $members = Database::fetchAll(
            'SELECT u.id, u.email, u.full_name, u.phone, u.last_login_at,
                    uo.role as org_role, uo.joined_at
             FROM user_organizations uo
             JOIN users u ON u.id = uo.user_id
             WHERE uo.organization_id = ? AND u.is_active = 1
             ORDER BY uo.role, u.full_name',
            [$this->getCurrentOrgId()]
        );

        $this->jsonSuccess($members);
    }

    /**
     * POST /api/organizations/{id}/invite
     */
    public function inviteMember(int $id): void
    {
        $this->requireOrgRole(['org_admin']);
        $this->validateRequired(['email', 'role']);

        $email = strtolower(trim($this->getParam('email')));
        $role = $this->getParam('role');

        $validRoles = ['org_admin', 'compliance_manager', 'board_member', 'auditor', 'employee'];
        if (!in_array($role, $validRoles)) {
            $this->jsonError('Ruolo non valido', 400, 'INVALID_ROLE');
        }

        // Trova utente per email
        $user = Database::fetchOne('SELECT id FROM users WHERE email = ?', [$email]);

        if (!$user) {
            $this->jsonError(
                'Utente non registrato. L\'utente deve prima registrarsi sulla piattaforma.',
                404,
                'USER_NOT_FOUND'
            );
        }

        // Verifica se già membro
        $existing = Database::fetchOne(
            'SELECT id FROM user_organizations WHERE user_id = ? AND organization_id = ?',
            [$user['id'], $this->getCurrentOrgId()]
        );

        if ($existing) {
            $this->jsonError('L\'utente è già membro di questa organizzazione', 409, 'ALREADY_MEMBER');
        }

        Database::insert('user_organizations', [
            'user_id'         => $user['id'],
            'organization_id' => $this->getCurrentOrgId(),
            'role'            => $role,
            'is_primary'      => 0,
        ]);

        $this->logAudit('member_invited', 'organization', $this->getCurrentOrgId(), [
            'invited_user_id' => $user['id'], 'role' => $role
        ]);

        $this->jsonSuccess([
            'user_id' => $user['id'],
            'role'    => $role,
        ], 'Membro aggiunto');
    }

    /**
     * DELETE /api/organizations/{id}/members/{userId}
     */
    public function removeMember(int $orgId, int $userId): void
    {
        $this->requireOrgRole(['org_admin']);

        // Non puoi rimuovere te stesso
        if ($userId === $this->getCurrentUserId()) {
            $this->jsonError('Non puoi rimuovere te stesso dall\'organizzazione', 400, 'CANNOT_REMOVE_SELF');
        }

        $deleted = Database::delete(
            'user_organizations',
            'user_id = ? AND organization_id = ?',
            [$userId, $this->getCurrentOrgId()]
        );

        if ($deleted === 0) {
            $this->jsonError('Membro non trovato', 404, 'MEMBER_NOT_FOUND');
        }

        $this->logAudit('member_removed', 'organization', $this->getCurrentOrgId(), [
            'removed_user_id' => $userId
        ]);

        $this->jsonSuccess(null, 'Membro rimosso');
    }

    /**
     * POST /api/organizations/classify
     * Classifica se l'organizzazione è Essential o Important secondo NIS2
     */
    public function classifyEntity(): void
    {
        $this->validateRequired(['sector', 'employee_count', 'annual_turnover_eur']);

        $sector = $this->getParam('sector');
        $employees = (int) $this->getParam('employee_count');
        $turnover = (float) $this->getParam('annual_turnover_eur');

        $entityType = $this->classifyNis2Entity($sector, $employees, $turnover);

        $this->jsonSuccess([
            'entity_type'       => $entityType,
            'sector'            => $sector,
            'employee_count'    => $employees,
            'annual_turnover_eur' => $turnover,
            'allows_voluntary'  => $entityType === 'not_applicable',
            'explanation'       => $this->getClassificationExplanation($entityType, $sector, $employees, $turnover),
        ]);
    }

    // ═══════════════════════════════════════════════════════════════════════
    // METODI PRIVATI
    // ═══════════════════════════════════════════════════════════════════════

    /**
     * Classifica entità NIS2 in base a settore, dipendenti e fatturato
     */
    private function classifyNis2Entity(string $sector, int $employees, float $turnover): string
    {
        // Settori Essenziali (Allegato I)
        $essentialSectors = [
            'energy', 'transport', 'banking', 'health', 'water',
            'digital_infra', 'public_admin', 'space',
        ];

        // Settori Importanti (Allegato II)
        $importantSectors = [
            'manufacturing', 'postal', 'chemical', 'food', 'waste',
            'ict_services', 'digital_providers', 'research',
        ];

        // Soglie dimensionali
        $isLarge = $employees >= 250 || $turnover >= 50000000;
        $isMedium = ($employees >= 50 || $turnover >= 10000000) && !$isLarge;

        if (in_array($sector, $essentialSectors)) {
            if ($isLarge) return 'essential';
            if ($isMedium) return 'important';
        }

        if (in_array($sector, $importantSectors)) {
            if ($isLarge || $isMedium) return 'important';
        }

        return 'not_applicable';
    }

    /**
     * Genera spiegazione della classificazione
     */
    private function getClassificationExplanation(string $type, string $sector, int $employees, float $turnover): string
    {
        $sizeLabel = $employees >= 250 ? 'grande impresa' : ($employees >= 50 ? 'media impresa' : 'piccola impresa');

        return match ($type) {
            'essential' => "L'organizzazione opera nel settore '{$sector}' (Allegato I NIS2) ed è classificata come {$sizeLabel}. Rientra tra le entità ESSENZIALI soggette a supervisione proattiva. Sanzioni fino a EUR 10M o 2% del fatturato globale.",
            'important' => "L'organizzazione opera nel settore '{$sector}' ed è classificata come {$sizeLabel}. Rientra tra le entità IMPORTANTI soggette a supervisione reattiva. Sanzioni fino a EUR 7M o 1,4% del fatturato globale.",
            default => "In base ai parametri forniti ({$sizeLabel}, settore '{$sector}'), l'organizzazione NON rientra attualmente nell'ambito di applicazione della NIS2. Si consiglia comunque di adottare le best practice di cybersecurity.",
        };
    }

    /**
     * Inizializza i controlli di compliance NIS2 per una nuova organizzazione
     */
    private function initializeComplianceControls(int $orgId): void
    {
        $controls = [
            ['NIS2-21.2.a', 'nis2', 'Politiche di analisi dei rischi e sicurezza dei sistemi informatici'],
            ['NIS2-21.2.b', 'nis2', 'Gestione degli incidenti'],
            ['NIS2-21.2.c', 'nis2', 'Continuità operativa e gestione delle crisi'],
            ['NIS2-21.2.d', 'nis2', 'Sicurezza della catena di approvvigionamento'],
            ['NIS2-21.2.e', 'nis2', 'Sicurezza acquisizione, sviluppo e manutenzione sistemi'],
            ['NIS2-21.2.f', 'nis2', 'Politiche e procedure per valutare efficacia misure'],
            ['NIS2-21.2.g', 'nis2', 'Pratiche di igiene informatica di base e formazione'],
            ['NIS2-21.2.h', 'nis2', 'Politiche e procedure relative alla crittografia'],
            ['NIS2-21.2.i', 'nis2', 'Sicurezza risorse umane, controllo accessi e gestione asset'],
            ['NIS2-21.2.j', 'nis2', 'Autenticazione multi-fattore e comunicazioni sicure'],
            ['NIS2-20.1', 'nis2', 'Governance: approvazione misure da parte degli organi di gestione'],
            ['NIS2-20.2', 'nis2', 'Formazione obbligatoria per gli organi di gestione'],
            ['NIS2-23.1', 'nis2', 'Notifica incidenti significativi al CSIRT (24h/72h/30gg)'],
        ];

        foreach ($controls as [$code, $framework, $title]) {
            Database::insert('compliance_controls', [
                'organization_id' => $orgId,
                'control_code'    => $code,
                'framework'       => $framework,
                'title'           => $title,
                'status'          => 'not_started',
            ]);
        }
    }

    // ══════════════════════════════════════════════════════════════════════
    // CONNETTORI PER-AZIENDA (Evidence Automation) — config NON segreta
    // I segreti vivono SOLO nel vault-steward (caricati via CLI admin); qui
    // si salva solo config non sensibile + alias della chiave vault.
    // ══════════════════════════════════════════════════════════════════════

    private const CONNECTOR_TYPES = ['m365', 'google', 'aws', 'azure', 'idp', 'edr', 'siem', 'ticketing'];

    /**
     * Autorizza la gestione dei connettori dell'org $orgId (parametro di ROUTE).
     * L'autorizzazione è ancorata a $orgId, NON all'header X-Organization-Id,
     * e usa il ruolo PER-ORG (user_organizations.role), non users.role globale.
     */
    private function connectorOrgGuard(int $orgId): void
    {
        $this->requireAuth();
        $user = $this->currentUser;

        // super_admin: accesso pieno
        if (($user['role'] ?? '') === 'super_admin') {
            return;
        }

        // Ruolo dell'utente NELL'org target (route {id})
        $membership = Database::fetchOne(
            'SELECT role FROM user_organizations WHERE user_id = ? AND organization_id = ?',
            [$user['id'], $orgId]
        );
        $manageRoles = ['org_admin', 'compliance_manager'];
        if ($membership && in_array($membership['role'], $manageRoles, true)) {
            return;
        }

        // Consulente dello studio che gestisce l'org (org.consulting_firm_id == user.consulting_firm_id)
        $firmId = $user['consulting_firm_id'] ?? null;
        if ($firmId) {
            $owned = Database::fetchOne(
                'SELECT id FROM organizations WHERE id = ? AND consulting_firm_id = ?',
                [$orgId, $firmId]
            );
            // consentito se l'utente è consulente/admin del firm (membership consultant sull'org o ruolo di gestione nel firm)
            $isFirmManager = ($membership && in_array($membership['role'], ['consultant', 'org_admin', 'compliance_manager'], true));
            if ($owned && $isFirmManager) {
                return;
            }
        }

        $this->jsonError('Non autorizzato a gestire i connettori di questa organizzazione', 403, 'CONNECTOR_FORBIDDEN');
    }

    /** GET /api/organizations/{id}/connectors */
    public function listConnectors(int $id): void
    {
        $this->connectorOrgGuard($id);
        $rows = Database::fetchAll(
            'SELECT id, connector_type, display_name, enabled, config, vault_key_alias,
                    secret_status, last_status, last_checked_at, updated_at
             FROM org_connectors WHERE organization_id = ? ORDER BY connector_type',
            [$id]
        );
        foreach ($rows as &$r) {
            $r['config'] = $r['config'] ? json_decode($r['config'], true) : new stdClass();
            $r['enabled'] = (bool) $r['enabled'];
        }
        unset($r);
        $this->jsonSuccess([
            'organization_id' => $id,
            'available_types' => self::CONNECTOR_TYPES,
            'connectors'      => $rows,
        ]);
    }

    /** PUT /api/organizations/{id}/connectors — body: {type, display_name?, enabled?, config?, vault_key_alias?, secret_status?} */
    public function saveConnector(int $id): void
    {
        $this->connectorOrgGuard($id);
        $type = strtolower((string) $this->getParam('type'));
        if (!in_array($type, self::CONNECTOR_TYPES, true)) {
            $this->jsonError('Tipo connettore non valido', 422, 'INVALID_TYPE');
        }

        $config = $this->getParam('config');
        if (is_string($config)) { $config = json_decode($config, true); }
        if (!is_array($config)) { $config = []; }
        // ALLOWLIST: persistiamo SOLO i campi non sensibili noti. Qualsiasi altra
        // chiave (inclusi segreti comunque nominati o annidati) viene scartata.
        // I segreti vivono solo nel vault — vedi cli_hint.
        $config = self::sanitizeConnectorConfig($config);

        $alias = $this->getParam('vault_key_alias');
        if ($alias === null || $alias === '') {
            $alias = 'tier1__nis2-app__connector_' . $type . '_org' . $id;
        }
        $secretStatus = $this->getParam('secret_status');
        if (!in_array($secretStatus, ['not_set', 'pending', 'configured'], true)) {
            $secretStatus = null;
        }

        $existing = Database::fetchOne(
            'SELECT id FROM org_connectors WHERE organization_id = ? AND connector_type = ?',
            [$id, $type]
        );

        $data = [
            'display_name'    => $this->getParam('display_name'),
            'enabled'         => $this->getParam('enabled') ? 1 : 0,
            'config'          => json_encode($config, JSON_UNESCAPED_UNICODE),
            'vault_key_alias' => substr($alias, 0, 190),
        ];
        if ($secretStatus !== null) { $data['secret_status'] = $secretStatus; }

        if ($existing) {
            $sets = []; $vals = [];
            foreach ($data as $k => $v) { $sets[] = "$k = ?"; $vals[] = $v; }
            $vals[] = $existing['id'];
            Database::query('UPDATE org_connectors SET ' . implode(', ', $sets) . ' WHERE id = ?', $vals);
            $connId = (int) $existing['id'];
        } else {
            $data['organization_id'] = $id;
            $data['connector_type']  = $type;
            $data['created_by']      = $this->getCurrentUserId();
            if (!isset($data['secret_status'])) { $data['secret_status'] = 'not_set'; }
            $connId = Database::insert('org_connectors', $data);
        }

        $this->logAudit('connector_configured', 'organization', $id, ['type' => $type, 'enabled' => $data['enabled']]);
        $this->jsonSuccess([
            'id'              => $connId,
            'connector_type'  => $type,
            'vault_key_alias' => $data['vault_key_alias'],
            'cli_hint'        => 'Carica il segreto nel vault: docker exec vault-steward node cli/vault-cli.js migrate ' . $alias . ' <key> <value>',
        ], 'Connettore salvato', $existing ? 200 : 201);
    }

    /**
     * Allowlist dei soli campi di configurazione NON sensibili ammessi per i
     * connettori. Tutto ciò che non è in lista (segreti, chiavi annidate, campi
     * arbitrari) viene scartato — robusto contro denylist-bypass (case, nesting,
     * nomi alternativi). I valori sono forzati a stringa e troncati.
     */
    private static function sanitizeConnectorConfig(array $config): array
    {
        $allowed = [
            'tenant_id', 'client_id', 'app_id', 'account_id', 'subscription_id',
            'region', 'base_url', 'endpoint', 'domain', 'workspace', 'instance_url',
            'scopes', 'project', 'directory_id', 'org_slug',
        ];
        $clean = [];
        foreach ($allowed as $k) {
            if (!array_key_exists($k, $config)) {
                continue;
            }
            $v = $config[$k];
            if ($k === 'scopes' && is_array($v)) {
                // lista di stringhe scope, max 30 voci
                $clean[$k] = array_slice(array_map(
                    fn($s) => substr((string) $s, 0, 120),
                    array_filter($v, 'is_scalar')
                ), 0, 30);
            } elseif (is_scalar($v)) {
                $clean[$k] = substr((string) $v, 0, 255);
            }
            // valori non scalari (oggetti/array annidati) su campi non-scopes: scartati
        }
        return $clean;
    }

    /** DELETE /api/organizations/{id}/connectors?type=xxx */
    public function deleteConnector(int $id): void
    {
        $this->connectorOrgGuard($id);
        $type = strtolower((string) ($this->getParam('type') ?? ($_GET['type'] ?? '')));
        if ($type === '') {
            $this->jsonError('Parametro type obbligatorio', 422, 'MISSING_TYPE');
        }
        Database::query(
            'DELETE FROM org_connectors WHERE organization_id = ? AND connector_type = ?',
            [$id, $type]
        );
        $this->logAudit('connector_removed', 'organization', $id, ['type' => $type]);
        $this->jsonSuccess(null, 'Connettore rimosso');
    }
}
