<?php

declare(strict_types=1);

namespace App\Controllers;

use App\ApiClient\AdminClient;
use App\ApiClient\ApiAuthException;
use App\ApiClient\ApiException;
use App\Auth\SessionManager;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Views\Twig;

/**
 * `/app/settings` — admin-only effective config + jobs status.
 *
 * Three sections render: Configuration (from `/admin/config`), Jobs
 * (from `/admin/jobs/status`, with overdue badges + manual-trigger
 * buttons), GeoIP (parsed out of the config payload).
 *
 * The trigger handler is a server-rendered POST → 303 → re-render
 * pattern: forwards to the api's `POST /admin/jobs/trigger/{name}` and
 * stashes the outcome in flash. We deliberately avoid an XHR + spinner
 * to keep the no-JS path working; the admin can wait the few seconds
 * a job takes.
 *
 * RBAC: Viewer/Operator get a no-access page; Admin only.
 */
final class SettingsController
{
    use CrudControllerSupport;

    public const PROJECT_VERSION = '0.9';

    public function __construct(
        private readonly Twig $twigEngine,
        private readonly SessionManager $sessionManager,
        private readonly AdminClient $admin,
    ) {
    }

    protected function twig(): Twig
    {
        return $this->twigEngine;
    }

    protected function sessions(): SessionManager
    {
        return $this->sessionManager;
    }

    public function index(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        if (($redirect = $this->requireUser($request, $response)) !== null) {
            return $redirect;
        }
        $user = $this->sessions()->getUser();
        if ($user === null) {
            return $response->withStatus(302)->withHeader('Location', '/login');
        }

        if (!$this->userIs($user, 'admin')) {
            return $response->withStatus(303)->withHeader('Location', '/no-access');
        }

        $config = null;
        $jobs = null;
        $appSettings = null;
        $error = null;
        try {
            $config = $this->admin->getConfig($user->userId);
            $jobs = $this->admin->getJobsStatus($user->userId);
            $appSettings = $this->admin->getAppSettings($user->userId);
        } catch (ApiAuthException) {
            return $response->withStatus(303)->withHeader('Location', '/no-access');
        } catch (ApiException $e) {
            $error = 'API error: ' . $e->getMessage();
        }

        return $this->twigEngine->render($response, 'pages/settings/index.twig', [
            'active_section' => 'settings',
            'config' => $config,
            'jobs' => $jobs,
            'app_settings' => $appSettings,
            'project_version' => self::PROJECT_VERSION,
            'error' => $error,
        ]);
    }

    /**
     * Persist the audit-emission toggles. Unchecked checkboxes don't
     * appear in the form body, so we explicitly map every known key to
     * a boolean and PATCH the snapshot.
     */
    public function updateAuditToggles(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        if (($redirect = $this->requireUser($request, $response)) !== null) {
            return $redirect;
        }
        $user = $this->sessions()->getUser();
        if ($user === null) {
            return $response->withStatus(302)->withHeader('Location', '/login');
        }
        if (!$this->userIs($user, 'admin')) {
            return $response->withStatus(303)->withHeader('Location', '/no-access');
        }

        $body = $this->formBody($request);
        $payload = [
            'audit_report_received_enabled' => isset($body['audit_report_received_enabled']),
            'audit_blocklist_request_enabled' => isset($body['audit_blocklist_request_enabled']),
        ];

        try {
            $this->admin->updateAppSettings($user->userId, $payload);
            $this->sessions()->flash('success', 'Audit toggles updated.');
        } catch (ApiException $e) {
            $this->flashFromException($e);
        }

        return $response->withStatus(303)->withHeader('Location', '/app/settings');
    }

    /**
     * @param array{name: string} $args
     */
    public function trigger(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
    {
        if (($redirect = $this->requireUser($request, $response)) !== null) {
            return $redirect;
        }
        $user = $this->sessions()->getUser();
        if ($user === null) {
            return $response->withStatus(302)->withHeader('Location', '/login');
        }
        if (!$this->userIs($user, 'admin')) {
            return $response->withStatus(303)->withHeader('Location', '/no-access');
        }

        $name = $args['name'];
        $body = $this->formBody($request);
        $params = [];
        if (isset($body['full'])) {
            $params['full'] = $this->formBool($body['full']);
        }
        if (isset($body['reenrich'])) {
            $params['reenrich'] = $this->formBool($body['reenrich']);
        }

        try {
            $result = $this->admin->triggerJob($user->userId, $name, $params);
            $status = (string) ($result['status'] ?? 'unknown');
            $items = (int) ($result['items_processed'] ?? 0);
            $duration = (int) ($result['duration_ms'] ?? 0);
            $this->sessions()->flash(
                $status === 'success' ? 'success' : 'error',
                sprintf('%s — %s (items=%d, duration=%dms)', $name, $status, $items, $duration),
            );
        } catch (ApiException $e) {
            $this->flashFromException($e);
        }

        return $response->withStatus(303)->withHeader('Location', '/app/settings');
    }

    /**
     * Wipe operational data. The API requires the literal `PURGE` string;
     * we additionally require the user to have typed it in the form, both
     * to avoid drive-by clicks and to keep curl-style misuse loud.
     */
    public function purge(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        if (($redirect = $this->requireUser($request, $response)) !== null) {
            return $redirect;
        }
        $user = $this->sessions()->getUser();
        if ($user === null) {
            return $response->withStatus(302)->withHeader('Location', '/login');
        }
        if (!$this->userIs($user, 'admin')) {
            return $response->withStatus(303)->withHeader('Location', '/no-access');
        }

        $body = $this->formBody($request);
        $confirm = isset($body['confirm']) && is_string($body['confirm']) ? trim($body['confirm']) : '';
        if ($confirm !== 'PURGE') {
            $this->sessions()->flash('error', 'Type PURGE exactly to confirm the wipe.');

            return $response->withStatus(303)->withHeader('Location', '/app/settings');
        }

        try {
            $result = $this->admin->purgeData($user->userId);
            $deleted = is_array($result['deleted'] ?? null) ? $result['deleted'] : [];
            $total = array_sum(array_map('intval', $deleted));
            $this->sessions()->flash('success', sprintf(
                'Database purged. %d rows deleted across %d tables.',
                $total,
                count($deleted),
            ));
        } catch (ApiException $e) {
            $this->flashFromException($e);
        }

        return $response->withStatus(303)->withHeader('Location', '/app/settings');
    }

    /**
     * Load the demo dataset. Idempotent on the api side — repeats return
     * 409 which we surface as an info-level flash, not an error.
     *
     * The api requires the literal `SEED` string (SEC_REVIEW F15); we
     * additionally require the user to have typed it in the form, both to
     * avoid drive-by clicks and to keep curl-style misuse loud — same
     * pattern as `purge` above.
     */
    public function seedDemo(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        if (($redirect = $this->requireUser($request, $response)) !== null) {
            return $redirect;
        }
        $user = $this->sessions()->getUser();
        if ($user === null) {
            return $response->withStatus(302)->withHeader('Location', '/login');
        }
        if (!$this->userIs($user, 'admin')) {
            return $response->withStatus(303)->withHeader('Location', '/no-access');
        }

        $body = $this->formBody($request);
        $confirm = isset($body['confirm']) && is_string($body['confirm']) ? trim($body['confirm']) : '';
        if ($confirm !== 'SEED') {
            $this->sessions()->flash('error', 'Type SEED exactly to confirm the demo seed.');

            return $response->withStatus(303)->withHeader('Location', '/app/settings');
        }

        try {
            $result = $this->admin->seedDemo($user->userId);
            $summary = is_array($result['summary'] ?? null) ? $result['summary'] : [];
            $this->sessions()->flash('success', sprintf(
                'Demo data loaded — %d reporters, %d consumers, %d IPs, %d reports.',
                (int) ($summary['reporters'] ?? 0),
                (int) ($summary['consumers'] ?? 0),
                (int) ($summary['ips'] ?? 0),
                (int) ($summary['reports'] ?? 0),
            ));
        } catch (ApiException $e) {
            $this->flashFromException($e);
        }

        return $response->withStatus(303)->withHeader('Location', '/app/settings');
    }
}
