<?php
declare(strict_types=1);

class LdapAdminController
{
    public static function index(): void
    {
        Auth::requireAdmin();
        $errors  = [];
        $success = Helper::flash('success');
        $error   = Helper::flash('error');
        $testResult = null;

        if (Helper::isPost()) {
            Helper::verifyCsrf();
            $action = Helper::post('action', 'save');

            if ($action === 'save') {
                self::handleSave($errors);
                if (empty($errors)) Helper::redirect('/admin/plugins/ldap', ['success' => Lang::get('ldap_settings_saved')]);
            } elseif ($action === 'upload_ca') {
                self::handleCaUpload($errors);
                if (empty($errors)) Helper::redirect('/admin/plugins/ldap', ['success' => Lang::get('ldap_settings_saved')]);
            } elseif ($action === 'remove_ca') {
                self::handleCaRemove();
                Helper::redirect('/admin/plugins/ldap', ['success' => Lang::get('ldap_settings_saved')]);
            } elseif ($action === 'test_bind') {
                $testResult = self::handleTestBind();
                // No redirect — the test outcome is rendered inline so the form keeps its state.
            } elseif ($action === 'run_sync') {
                $r = LdapSync::run();
                if (($r['ok'] ?? false) === true) {
                    Helper::redirect('/admin/plugins/ldap', ['success' => Lang::get('ldap_sync_done', ['n' => (string)($r['checked'] ?? 0), 'd' => (string)($r['disabled'] ?? 0)])]);
                }
                Helper::redirect('/admin/plugins/ldap', ['error' => Lang::get('ldap_sync_failed', ['reason' => (string)($r['reason'] ?? '?')])]);
            }
        }

        $cfg = self::loadCfgForView();
        view('admin/ldap', compact('cfg', 'errors', 'success', 'error', 'testResult'));
    }

    // ── Actions ──────────────────────────────────────────────────────────────

    private static function handleSave(array &$errors): void
    {
        $enabled    = Helper::post('ldap_enabled', '0') === '1' ? '1' : '0';
        $transport  = Helper::post('ldap_transport', 'ldaps');
        if (!in_array($transport, ['ldaps', 'starttls', 'cleartext'], true)) {
            $errors[] = Lang::get('ldap_invalid_transport');
            return;
        }
        $ackCleartext = Helper::post('ldap_cleartext_ack', '') === '1';
        if ($transport === 'cleartext' && !$ackCleartext) {
            $errors[] = Lang::get('ldap_cleartext_must_ack');
            return;
        }

        $host       = trim(Helper::post('ldap_host', ''));
        $portRaw    = trim(Helper::post('ldap_port', ''));
        $port       = $portRaw === '' ? ($transport === 'ldaps' ? 636 : 389) : (int)$portRaw;
        if ($port < 1 || $port > 65535) {
            $errors[] = Lang::get('ldap_invalid_port');
            return;
        }
        $baseDn     = trim(Helper::post('ldap_base_dn', ''));
        $bindDn     = trim(Helper::post('ldap_bind_dn', ''));
        $bindPwNew  = Helper::post('ldap_bind_password', '');
        $userFilter = trim(Helper::post('ldap_user_filter', '(mail=%s)'));
        if ($userFilter !== '' && !str_contains($userFilter, '%s')) {
            $errors[] = Lang::get('ldap_user_filter_needs_placeholder');
            return;
        }
        $activeFilter = trim(Helper::post('ldap_active_filter', ''));
        $attrUser     = trim(Helper::post('ldap_attr_username', 'sAMAccountName'));
        $attrEmail    = trim(Helper::post('ldap_attr_email', 'mail'));
        $attrGroups   = trim(Helper::post('ldap_attr_groups', 'memberOf'));
        $adminGroup   = trim(Helper::post('ldap_admin_group_dn', ''));
        $reqGroup     = trim(Helper::post('ldap_required_group_dn', ''));
        $tlsRequire   = Helper::post('ldap_tls_require_cert', 'demand');
        if (!in_array($tlsRequire, ['demand', 'try', 'allow', 'never'], true)) $tlsRequire = 'demand';
        $takeover     = Helper::post('ldap_takeover_existing_users', '0') === '1' ? '1' : '0';

        Helper::setSetting('ldap_enabled',                   $enabled);
        Helper::setSetting('ldap_transport',                 $transport);
        Helper::setSetting('ldap_host',                      $host);
        Helper::setSetting('ldap_port',                      (string)$port);
        Helper::setSetting('ldap_base_dn',                   $baseDn);
        Helper::setSetting('ldap_bind_dn',                   $bindDn);
        if ($bindPwNew !== '') {
            Helper::setSetting('ldap_bind_password_enc', Crypto::encrypt($bindPwNew));
        }
        Helper::setSetting('ldap_user_filter',               $userFilter ?: '(mail=%s)');
        Helper::setSetting('ldap_active_filter',             $activeFilter);
        Helper::setSetting('ldap_attr_username',             $attrUser ?: 'sAMAccountName');
        Helper::setSetting('ldap_attr_email',                $attrEmail ?: 'mail');
        Helper::setSetting('ldap_attr_groups',               $attrGroups ?: 'memberOf');
        Helper::setSetting('ldap_admin_group_dn',            $adminGroup);
        Helper::setSetting('ldap_required_group_dn',         $reqGroup);
        Helper::setSetting('ldap_tls_require_cert',          $tlsRequire);
        Helper::setSetting('ldap_takeover_existing_users',   $takeover);

        // Cleartext acknowledgement timestamp — used by HealthCheck to silence
        // email-alerts while the compliance status stays error.
        if ($transport === 'cleartext' && $ackCleartext) {
            Helper::setSetting('ldap_cleartext_acknowledged_at', (string)time());
        }
        if ($transport !== 'cleartext') {
            Helper::setSetting('ldap_cleartext_acknowledged_at', '0');
        }

        SecurityLog::log('ldap_settings_changed', SecurityLog::SEV_WARN, [
            'transport' => $transport,
            'enabled'   => $enabled,
        ]);
    }

    private static function handleCaUpload(array &$errors): void
    {
        if (!isset($_FILES['ldap_ca']) || $_FILES['ldap_ca']['error'] !== UPLOAD_ERR_OK) {
            $errors[] = Lang::get('ldap_ca_upload_missing');
            return;
        }
        $file = $_FILES['ldap_ca'];
        if ($file['size'] > 32 * 1024) {
            $errors[] = Lang::get('ldap_ca_too_large');
            return;
        }
        if (!is_uploaded_file($file['tmp_name'])) {
            $errors[] = Lang::get('ldap_ca_upload_missing');
            return;
        }
        $pem = file_get_contents($file['tmp_name']);
        if ($pem === false || !str_contains($pem, '-----BEGIN CERTIFICATE-----')) {
            $errors[] = Lang::get('ldap_ca_not_pem');
            return;
        }
        $parsed = @openssl_x509_parse($pem);
        if (!$parsed || empty($parsed['validTo_time_t'])) {
            $errors[] = Lang::get('ldap_ca_not_pem');
            return;
        }
        if ($parsed['validTo_time_t'] < time()) {
            $errors[] = Lang::get('ldap_ca_expired');
            return;
        }

        $target = DATA_PATH . '/ldap_ca.pem';
        if (file_put_contents($target, $pem) === false) {
            $errors[] = Lang::get('ldap_ca_write_failed');
            return;
        }
        @chmod($target, 0600);

        Helper::setSetting('ldap_ca_path',        $target);
        Helper::setSetting('ldap_ca_subject',     (string)($parsed['name'] ?? ''));
        Helper::setSetting('ldap_ca_valid_to',    (string)$parsed['validTo_time_t']);
        Helper::setSetting('ldap_ca_uploaded_at', (string)time());

        SecurityLog::log('ldap_ca_uploaded', SecurityLog::SEV_WARN, [
            'subject'  => (string)($parsed['name'] ?? ''),
            'valid_to' => date('Y-m-d', (int)$parsed['validTo_time_t']),
        ]);
    }

    private static function handleCaRemove(): void
    {
        $path = (string)Helper::getSetting('ldap_ca_path', '');
        if ($path !== '' && is_file($path) && str_starts_with($path, DATA_PATH . '/')) {
            @unlink($path);
        }
        Helper::setSetting('ldap_ca_path',        '');
        Helper::setSetting('ldap_ca_subject',     '');
        Helper::setSetting('ldap_ca_valid_to',    '0');
        Helper::setSetting('ldap_ca_uploaded_at', '0');
        SecurityLog::log('ldap_ca_removed', SecurityLog::SEV_WARN);
    }

    /**
     * Service-account bind probe. Does NOT verify a user password — it only
     * checks that connection, TLS and the bind credentials are usable. Used
     * to give the admin an immediate green/red signal in the form.
     */
    private static function handleTestBind(): array
    {
        $cfg = LdapAuthAdapter::config();
        if ($cfg === null) {
            return ['ok' => false, 'msg' => Lang::get('ldap_test_not_configured')];
        }
        $ds = LdapAuthAdapter::connect($cfg);
        if (!$ds) {
            return ['ok' => false, 'msg' => Lang::get('ldap_test_connect_failed')];
        }
        try {
            if ($cfg['bind_dn'] === '') {
                @ldap_unbind($ds);
                return ['ok' => true, 'msg' => Lang::get('ldap_test_anonymous_ok')];
            }
            $pw = $cfg['bind_password_enc'] !== '' ? Crypto::decrypt($cfg['bind_password_enc']) : '';
            if (@ldap_bind($ds, $cfg['bind_dn'], $pw)) {
                return ['ok' => true, 'msg' => Lang::get('ldap_test_bind_ok')];
            }
            return ['ok' => false, 'msg' => Lang::get('ldap_test_bind_failed')];
        } finally {
            @ldap_unbind($ds);
        }
    }

    // ── View data ────────────────────────────────────────────────────────────

    private static function loadCfgForView(): array
    {
        return [
            'enabled'             => Helper::getSetting('ldap_enabled', '0') === '1',
            'transport'           => (string)Helper::getSetting('ldap_transport', 'ldaps'),
            'host'                => (string)Helper::getSetting('ldap_host', ''),
            'port'                => (string)Helper::getSetting('ldap_port', '636'),
            'base_dn'             => (string)Helper::getSetting('ldap_base_dn', ''),
            'bind_dn'             => (string)Helper::getSetting('ldap_bind_dn', ''),
            'bind_password_set'   => (string)Helper::getSetting('ldap_bind_password_enc', '') !== '',
            'user_filter'         => (string)Helper::getSetting('ldap_user_filter', '(mail=%s)'),
            'active_filter'       => (string)Helper::getSetting('ldap_active_filter', ''),
            'attr_username'       => (string)Helper::getSetting('ldap_attr_username', 'sAMAccountName'),
            'attr_email'          => (string)Helper::getSetting('ldap_attr_email', 'mail'),
            'attr_groups'         => (string)Helper::getSetting('ldap_attr_groups', 'memberOf'),
            'admin_group_dn'      => (string)Helper::getSetting('ldap_admin_group_dn', ''),
            'required_group_dn'   => (string)Helper::getSetting('ldap_required_group_dn', ''),
            'tls_require_cert'    => (string)Helper::getSetting('ldap_tls_require_cert', 'demand'),
            'takeover'            => Helper::getSetting('ldap_takeover_existing_users', '0') === '1',
            'ca_path'             => (string)Helper::getSetting('ldap_ca_path', ''),
            'ca_subject'          => (string)Helper::getSetting('ldap_ca_subject', ''),
            'ca_valid_to'         => (int)Helper::getSetting('ldap_ca_valid_to', '0'),
            'ca_uploaded_at'      => (int)Helper::getSetting('ldap_ca_uploaded_at', '0'),
            'sync_last_run'       => (int)Helper::getSetting('ldap_sync_last_run', '0'),
            'sync_last_checked'   => (int)Helper::getSetting('ldap_sync_last_checked', '0'),
            'sync_last_disabled'  => (int)Helper::getSetting('ldap_sync_last_disabled', '0'),
            'has_ldap_extension'  => extension_loaded('ldap'),
        ];
    }
}
