<?php

declare(strict_types=1);

namespace App\Controllers;

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

/**
 * `/app/ips` (list) and `/app/ips/{ip}` (detail).
 *
 * Both delegate to AdminClient; controller responsibility is parsing
 * query params, mapping API exceptions to UI states, and handing the
 * DTOs to Twig.
 *
 * The list page accepts `q`, `category`, `min_score`, `max_score`,
 * `country`, `asn`, `status`, `page`, `page_size` — all optional. They
 * round-trip through the form via the `filters` Twig variable so the
 * UI preserves the user's selection across pagination clicks.
 */
final class IpsController
{
    /** Categories list — small + bounded, kept for the dropdown. */
    private const CATEGORIES = ['brute_force', 'spam', 'scanner', 'malware_c2', 'web_attack'];
    private const STATUSES = ['scored', 'manual', 'allowlisted', 'clean'];

    public function __construct(
        private readonly Twig $twig,
        private readonly SessionManager $sessions,
        private readonly AdminClient $admin,
    ) {
    }

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

        $params = $request->getQueryParams();
        $filters = [
            'q' => $this->cleanString($params['q'] ?? null),
            'category' => $this->cleanString($params['category'] ?? null),
            'min_score' => $this->cleanString($params['min_score'] ?? null),
            'max_score' => $this->cleanString($params['max_score'] ?? null),
            'country' => $this->cleanString($params['country'] ?? null),
            'asn' => $this->cleanString($params['asn'] ?? null),
            'status' => $this->cleanString($params['status'] ?? null),
        ];
        $page = isset($params['page']) && ctype_digit((string) $params['page']) ? max(1, (int) $params['page']) : 1;
        $pageSize = 25;

        $list = null;
        $countries = [];
        $error = null;
        try {
            $list = $this->admin->searchIps($user->userId, $filters, $page, $pageSize);
            $countries = $this->admin->listCountries($user->userId);
        } catch (ApiValidationException $e) {
            $error = 'invalid filter: ' . implode(', ', array_keys($e->details));
        } catch (ApiException) {
            $error = 'API unreachable.';
        }

        return $this->twig->render($response, 'pages/ips/index.twig', [
            'active_section' => 'ips',
            'list' => $list,
            'filters' => $filters,
            'page' => $page,
            'categories' => self::CATEGORIES,
            'statuses' => self::STATUSES,
            'countries' => $countries,
            'error' => $error,
        ]);
    }

    /**
     * @param array{ip: string} $args
     */
    public function show(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
    {
        $user = $this->sessions->getUser();
        if ($user === null) {
            return $response->withStatus(302)->withHeader('Location', '/login');
        }

        try {
            $detail = $this->admin->getIp($user->userId, rawurldecode($args['ip']));
        } catch (ApiNotFoundException) {
            return $this->twig->render(
                $response->withStatus(404),
                'pages/error.twig',
                ['status' => 404, 'is_client_error' => true, 'message' => 'IP not found or invalid.']
            );
        } catch (ApiException $e) {
            return $this->twig->render(
                $response->withStatus(502),
                'pages/error.twig',
                ['status' => 502, 'is_client_error' => false, 'message' => $e->getMessage()]
            );
        }

        // Decay parameters drive the score-over-time chart on the detail
        // page. If the API can't provide them we fall through to JS defaults
        // (exponential, 14-day half-life) — chart degrades, page still renders.
        $categories = [];
        try {
            $catList = $this->admin->listCategories($user->userId);
            $items = $catList['items'] ?? [];
            if (is_array($items)) {
                foreach ($items as $c) {
                    if (!is_array($c) || !isset($c['slug'])) {
                        continue;
                    }
                    $categories[] = [
                        'slug' => (string) $c['slug'],
                        'decay_function' => (string) ($c['decay_function'] ?? 'exponential'),
                        'decay_param' => (float) ($c['decay_param'] ?? 14.0),
                    ];
                }
            }
        } catch (\Throwable) {
            // ignore — chart falls back to defaults
        }

        $reportEvents = [];
        foreach ($detail->history as $ev) {
            if (($ev['type'] ?? '') !== 'report') {
                continue;
            }
            if (!isset($ev['at'], $ev['category'])) {
                continue;
            }
            $reportEvents[] = [
                'at' => (string) $ev['at'],
                'category' => (string) $ev['category'],
                'weight' => (float) ($ev['weight'] ?? 1.0),
            ];
        }

        return $this->twig->render($response, 'pages/ips/detail.twig', [
            'active_section' => 'ips',
            'detail' => $detail,
            'can_write' => in_array($user->role, ['operator', 'admin'], true),
            'score_chart' => [
                'reports' => $reportEvents,
                'categories' => $categories,
                'now' => gmdate('c'),
            ],
        ]);
    }

    private function cleanString(mixed $value): ?string
    {
        if (!is_string($value)) {
            return null;
        }
        $trimmed = trim($value);

        return $trimmed === '' ? null : $trimmed;
    }
}
