<?php
declare(strict_types=1);

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

        if (Helper::isPost()) {
            Helper::verifyCsrf();
            $action = Helper::post('action', 'save');
            if ($action === 'save')        self::handleSaveGlobal($errors);
            elseif ($action === 'add')     self::handleAddProvider($errors);
            elseif ($action === 'update')  self::handleUpdateProvider($errors);
            elseif ($action === 'remove')  self::handleRemoveProvider();
            elseif ($action === 'refresh') self::handleRefreshDiscovery();
            if (empty($errors)) {
                Helper::redirect('/admin/plugins/oidc', ['success' => Lang::get('oidc_settings_saved')]);
            }
        }

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

    // ── Global settings (enable / takeover) ─────────────────────────────────

    private static function handleSaveGlobal(array &$errors): void
    {
        Helper::setSetting('oidc_enabled',                 Helper::post('oidc_enabled', '0') === '1' ? '1' : '0');
        Helper::setSetting('oidc_takeover_existing_users', Helper::post('oidc_takeover_existing_users', '0') === '1' ? '1' : '0');
        SecurityLog::log('oidc_settings_changed', SecurityLog::SEV_WARN, [
            'enabled' => Helper::post('oidc_enabled', '0'),
        ]);
    }

    // ── Provider CRUD ───────────────────────────────────────────────────────

    private static function handleAddProvider(array &$errors): void
    {
        $id = strtolower(trim((string)Helper::post('id', '')));
        if (!preg_match('/^[a-z0-9_-]+$/', $id)) { $errors[] = Lang::get('oidc_err_invalid_id'); return; }

        $providers = OidcConfig::all();
        foreach ($providers as $p) {
            if (($p['id'] ?? '') === $id) { $errors[] = Lang::get('oidc_err_id_taken'); return; }
        }
        $kind         = (string)Helper::post('kind', 'custom');
        $kindInput    = trim((string)Helper::post('kind_input', ''));
        $discoveryUrl = trim((string)Helper::post('discovery_url', ''));
        if ($discoveryUrl === '' && in_array($kind, ['google','microsoft','keycloak'], true)) {
            $discoveryUrl = OidcConfig::templateDiscoveryUrl($kind, $kindInput);
        }
        if (!self::isValidDiscoveryUrl($discoveryUrl)) {
            $errors[] = Lang::get('oidc_err_invalid_discovery'); return;
        }
        $clientId = trim((string)Helper::post('client_id', ''));
        if ($clientId === '') { $errors[] = Lang::get('oidc_err_client_id_required'); return; }
        $secret = (string)Helper::post('client_secret', '');
        if ($secret === '') { $errors[] = Lang::get('oidc_err_secret_required'); return; }

        $providers[] = [
            'id'                => $id,
            'kind'              => $kind,
            'label'             => trim((string)Helper::post('label', $id)) ?: $id,
            'discovery_url'     => $discoveryUrl,
            'client_id'         => $clientId,
            'client_secret_enc' => Crypto::encrypt($secret),
            'scopes'            => trim((string)Helper::post('scopes', 'openid email profile')) ?: 'openid email profile',
            'enabled'           => Helper::post('enabled', '1') === '1',
        ];
        OidcConfig::save($providers);
        OidcDiscovery::clearCache($id);
        SecurityLog::log('oidc_provider_added', SecurityLog::SEV_WARN, ['provider' => $id, 'kind' => $kind]);
    }

    private static function handleUpdateProvider(array &$errors): void
    {
        $id        = (string)Helper::post('id', '');
        $providers = OidcConfig::all();
        $found     = false;
        foreach ($providers as &$p) {
            if (($p['id'] ?? '') !== $id) continue;
            $found = true;
            $p['label']         = trim((string)Helper::post('label', $p['label'] ?? $id)) ?: ($p['label'] ?? $id);
            $p['discovery_url'] = trim((string)Helper::post('discovery_url', $p['discovery_url'] ?? ''));
            $p['client_id']     = trim((string)Helper::post('client_id', $p['client_id'] ?? ''));
            $newSecret = (string)Helper::post('client_secret', '');
            if ($newSecret !== '') {
                $p['client_secret_enc'] = Crypto::encrypt($newSecret);
            }
            $p['scopes']  = trim((string)Helper::post('scopes', $p['scopes'] ?? 'openid email profile')) ?: 'openid email profile';
            $p['enabled'] = Helper::post('enabled', '0') === '1';
            break;
        }
        unset($p);
        if (!$found) { $errors[] = Lang::get('oidc_err_unknown_provider'); return; }
        OidcConfig::save($providers);
        OidcDiscovery::clearCache($id);
        SecurityLog::log('oidc_provider_updated', SecurityLog::SEV_WARN, ['provider' => $id]);
    }

    private static function handleRemoveProvider(): void
    {
        $id = (string)Helper::post('id', '');
        $providers = array_values(array_filter(OidcConfig::all(), fn($p) => ($p['id'] ?? '') !== $id));
        OidcConfig::save($providers);
        OidcDiscovery::clearCache($id);
        SecurityLog::log('oidc_provider_removed', SecurityLog::SEV_WARN, ['provider' => $id]);
    }

    /**
     * Discovery URL must be HTTPS, end in `/.well-known/openid-configuration`,
     * and only allow plain http for localhost loopback (for local-test setups
     * without TLS). Anything else is rejected outright — running OIDC over
     * cleartext leaks the client secret and the ID token to anybody on path.
     */
    private static function isValidDiscoveryUrl(string $url): bool
    {
        if (!preg_match('#^(https?)://([^/]+)/(?:.*)\.well-known/openid-configuration$#i', $url, $m)) {
            return false;
        }
        $scheme = strtolower($m[1]);
        $host   = strtolower(parse_url($url, PHP_URL_HOST) ?: '');
        if ($scheme === 'https') return true;
        // Cleartext only for the unambiguous loopback names / IPs.
        return in_array($host, ['localhost', '127.0.0.1', '::1'], true);
    }

    private static function handleRefreshDiscovery(): void
    {
        $id = (string)Helper::post('id', '');
        OidcDiscovery::clearCache($id);
    }

    // ── View prep ───────────────────────────────────────────────────────────

    private static function loadCfgForView(): array
    {
        $providers = OidcConfig::all();
        // Mask the encrypted secret for the view — never echo even ciphertext.
        foreach ($providers as &$p) {
            $p['has_secret'] = !empty($p['client_secret_enc']);
            unset($p['client_secret_enc']);
        }
        unset($p);
        return [
            'enabled'      => Helper::getSetting('oidc_enabled', '0') === '1',
            'takeover'     => Helper::getSetting('oidc_takeover_existing_users', '0') === '1',
            'providers'    => $providers,
            'redirect_uri' => OidcConfig::redirectUri(),
        ];
    }
}
