<?php

declare(strict_types=1);

namespace App\Controllers;

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

/**
 * `/app/manual-blocks` and `/app/subnets` — the latter is a thin alias
 * that pre-filters to `kind=subnet` so the sidebar's "Subnets" link
 * lands somewhere meaningful.
 *
 * RBAC mirrors the api: list/show ⇒ Viewer, create/delete ⇒ Operator.
 * Buttons hide for viewers; the api enforces.
 */
final class ManualBlocksController
{
    use CrudControllerSupport;

    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
    {
        $redirect = $this->requireUser($request, $response);
        if ($redirect !== null) {
            return $redirect;
        }
        $user = $this->sessionManager->getUser();
        // requireUser already null-checked the user; assignment for type narrow.
        \assert($user !== null);

        $kind = $request->getQueryParams()['kind'] ?? null;
        if (!is_string($kind) || !in_array($kind, ['ip', 'subnet'], true)) {
            $kind = null;
        }

        try {
            $list = $this->admin->listManualBlocks($user->userId, $kind);
        } catch (ApiException $e) {
            $list = ['items' => [], 'total' => 0];
            $this->flashFromException($e);
        }

        return $this->twigEngine->render($response, 'pages/manual-blocks/index.twig', [
            'active_section' => 'manual-blocks',
            'list' => $list,
            'kind' => $kind,
            'can_write' => $this->userIs($user, 'operator', 'admin'),
        ]);
    }

    public function subnetsIndex(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        $redirect = $this->requireUser($request, $response);
        if ($redirect !== null) {
            return $redirect;
        }
        $user = $this->sessionManager->getUser();
        \assert($user !== null);

        try {
            $list = $this->admin->listManualBlocks($user->userId, 'subnet');
        } catch (ApiException $e) {
            $list = ['items' => [], 'total' => 0];
            $this->flashFromException($e);
        }

        return $this->twigEngine->render($response, 'pages/manual-blocks/index.twig', [
            'active_section' => 'subnets',
            'list' => $list,
            'kind' => 'subnet',
            'can_write' => $this->userIs($user, 'operator', 'admin'),
        ]);
    }

    public function create(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        $redirect = $this->requireUser($request, $response);
        if ($redirect !== null) {
            return $redirect;
        }
        $user = $this->sessionManager->getUser();
        \assert($user !== null);

        $body = $this->formBody($request);
        $kind = isset($body['kind']) && is_string($body['kind']) ? $body['kind'] : '';
        $payload = ['kind' => $kind];
        if ($kind === 'ip' && isset($body['ip']) && is_string($body['ip'])) {
            $payload['ip'] = trim($body['ip']);
        }
        if ($kind === 'subnet' && isset($body['cidr']) && is_string($body['cidr'])) {
            $payload['cidr'] = trim($body['cidr']);
        }
        if (isset($body['reason']) && is_string($body['reason']) && trim($body['reason']) !== '') {
            $payload['reason'] = trim($body['reason']);
        }
        if (isset($body['expires_at']) && is_string($body['expires_at']) && trim($body['expires_at']) !== '') {
            $expires = trim($body['expires_at']);
            // The form uses <input type="date"> which emits "YYYY-MM-DD";
            // expand to end-of-day so the block stays effective for the
            // whole selected calendar day. Existing datetime-local values
            // ("YYYY-MM-DDTHH:MM") are passed through.
            if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $expires) === 1) {
                $expires .= 'T23:59:59';
            }
            $payload['expires_at'] = $expires . 'Z';
        }

        try {
            $this->admin->createManualBlock($user->userId, $payload);
            $this->sessionManager->flash('success', 'Manual block added.');
        } catch (ApiException $e) {
            $this->flashFromException($e);
        }

        // Always redirect to /app/manual-blocks. Users can navigate
        // back to /app/subnets via the sidebar or kind filter; routing
        // POST→GET to a stable destination keeps tests + browser behaviour
        // predictable.
        return $response->withStatus(303)->withHeader('Location', '/app/manual-blocks');
    }

    /**
     * @param array{id: string} $args
     */
    public function delete(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
    {
        $redirect = $this->requireUser($request, $response);
        if ($redirect !== null) {
            return $redirect;
        }
        $user = $this->sessionManager->getUser();
        \assert($user !== null);

        $id = $this->parseId($args['id']);
        if ($id === null) {
            return $response->withStatus(303)->withHeader('Location', '/app/manual-blocks');
        }

        try {
            $this->admin->deleteManualBlock($user->userId, $id);
            $this->sessionManager->flash('success', 'Manual block removed.');
        } catch (ApiException $e) {
            $this->flashFromException($e);
        }

        // SEC_REVIEW F10: route the attacker-influenced `next` form field
        // through the same-origin path validator before using it as a
        // `Location` value.
        $next = SessionManager::safeNextOrDefault(
            $this->formBody($request)['next'] ?? null,
            '/app/manual-blocks',
        );

        return $response->withStatus(303)->withHeader('Location', $next);
    }
}
