<?php

declare(strict_types=1);

namespace Ekdos\Users;

use Ekdos\Auth\AuthService;
use Ekdos\Auth\SessionStore;
use Ekdos\Http\Middleware\SessionMiddleware;
use Ekdos\Support\Json;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

/**
 * Benutzerverwaltung.
 *
 * Alle Routen liegen hinter dem Recht "users.manage" (Rolle Administration).
 * Vier Regeln schuetzen davor, sich selbst auszusperren:
 *
 *   1. Der letzte aktive Administrator kann weder geloescht noch herabgestuft
 *      noch deaktiviert werden.
 *   2. Niemand kann das eigene Konto loeschen.
 *   3. Niemand kann die eigene Rolle aendern.
 *   4. Jede Rechteaenderung meldet den betroffenen Benutzer sofort ab, damit
 *      eine laufende Sitzung keine Rechte behaelt, die sie nicht mehr hat.
 */
final readonly class UserController
{
    public function __construct(
        private UserRepository $users,
        private SessionStore $sessions,
    ) {}

    public function index(Request $request, Response $response): Response
    {
        return Json::write($response, [
            'users' => array_map(static fn (User $user): array => $user->toArray(), $this->users->all()),
            'roles' => self::roleCatalogue(),
        ]);
    }

    public function create(Request $request, Response $response): Response
    {
        $actor = SessionMiddleware::of($request);
        $body = self::body($request);

        $username = trim((string) ($body['username'] ?? ''));
        $displayName = trim((string) ($body['displayName'] ?? ''));
        $password = (string) ($body['password'] ?? '');
        $role = Role::tryFrom((string) ($body['role'] ?? ''));

        if (($problem = self::validateName($username, $displayName)) !== null) {
            return Json::error($response, $problem, 400);
        }

        if ($role === null) {
            return Json::error($response, 'Bitte eine gültige Rolle auswählen.', 400);
        }

        if (($problem = AuthService::rejectWeakPassword($password)) !== null) {
            return Json::error($response, $problem, 400);
        }

        if ($this->users->usernameTaken($username)) {
            return Json::error($response, 'Dieser Benutzername ist bereits vergeben.', 409);
        }

        $user = $this->users->create($username, $displayName, $role, AuthService::hash($password));
        $this->users->audit($actor?->userId, $actor?->displayName ?? 'System', 'user.create', $user->id, $user->username, ['role' => $role->value]);

        return Json::write($response, ['user' => $user->toArray()], 201);
    }

    public function update(Request $request, Response $response, array $args): Response
    {
        $actor = SessionMiddleware::of($request);
        $id = (string) ($args['id'] ?? '');
        $user = $this->users->find($id);

        if ($user === null) {
            return Json::error($response, 'Dieser Benutzer existiert nicht.', 404);
        }

        $body = self::body($request);
        $fields = [];
        $changed = [];

        if (array_key_exists('displayName', $body)) {
            $displayName = trim((string) $body['displayName']);

            if ($displayName === '' || mb_strlen($displayName) > 120) {
                return Json::error($response, 'Der Anzeigename muss zwischen 1 und 120 Zeichen lang sein.', 400);
            }

            $fields['display_name'] = $displayName;
            $changed[] = 'Anzeigename';
        }

        if (array_key_exists('username', $body)) {
            $username = trim((string) $body['username']);

            if (($problem = self::validateName($username, 'x')) !== null) {
                return Json::error($response, $problem, 400);
            }

            if ($this->users->usernameTaken($username, $user->id)) {
                return Json::error($response, 'Dieser Benutzername ist bereits vergeben.', 409);
            }

            $fields['username'] = $username;
            $changed[] = 'Benutzername';
        }

        if (array_key_exists('role', $body)) {
            $role = Role::tryFrom((string) $body['role']);

            if ($role === null) {
                return Json::error($response, 'Bitte eine gültige Rolle auswählen.', 400);
            }

            if ($actor?->userId === $user->id && $role !== $user->role) {
                return Json::error($response, 'Die eigene Rolle kann nicht geändert werden.', 409);
            }

            if ($user->role === Role::Admin && $role !== Role::Admin && $this->users->countAdmins($user->id) === 0) {
                return Json::error($response, 'Es muss mindestens ein aktiver Administrator bestehen bleiben.', 409);
            }

            $fields['role'] = $role->value;
            $changed[] = 'Rolle';
        }

        if (array_key_exists('isActive', $body)) {
            $isActive = (bool) $body['isActive'];

            if (!$isActive && $user->role === Role::Admin && $this->users->countAdmins($user->id) === 0) {
                return Json::error($response, 'Der letzte aktive Administrator kann nicht deaktiviert werden.', 409);
            }

            if (!$isActive && $actor?->userId === $user->id) {
                return Json::error($response, 'Das eigene Konto kann nicht deaktiviert werden.', 409);
            }

            $fields['is_active'] = $isActive;
            $changed[] = $isActive ? 'aktiviert' : 'deaktiviert';
        }

        if (array_key_exists('password', $body) && (string) $body['password'] !== '') {
            $password = (string) $body['password'];

            if (($problem = AuthService::rejectWeakPassword($password)) !== null) {
                return Json::error($response, $problem, 400);
            }

            $fields['password_hash'] = AuthService::hash($password);
            $changed[] = 'Passwort';
        }

        if ($fields === []) {
            return Json::error($response, 'Es wurde nichts geändert.', 400);
        }

        $updated = $this->users->update($user->id, $fields);
        $this->users->audit($actor?->userId, $actor?->displayName ?? 'System', 'user.update', $user->id, $user->username, ['changed' => $changed]);

        // Rolle, Aktivierung oder Passwort geaendert: laufende Sitzungen beenden.
        $forcesLogout = isset($fields['role']) || isset($fields['is_active']) || isset($fields['password_hash']);
        $endedSessions = $forcesLogout ? $this->sessions->destroyAllFor($user->id) : 0;

        return Json::write($response, [
            'user' => $updated?->toArray(),
            'endedSessions' => $endedSessions,
        ]);
    }

    public function delete(Request $request, Response $response, array $args): Response
    {
        $actor = SessionMiddleware::of($request);
        $id = (string) ($args['id'] ?? '');
        $user = $this->users->find($id);

        if ($user === null) {
            return Json::error($response, 'Dieser Benutzer existiert nicht.', 404);
        }

        if ($actor?->userId === $user->id) {
            return Json::error($response, 'Das eigene Konto kann nicht gelöscht werden.', 409);
        }

        if ($user->role === Role::Admin && $this->users->countAdmins($user->id) === 0) {
            return Json::error($response, 'Der letzte aktive Administrator kann nicht gelöscht werden.', 409);
        }

        // Erst abmelden, dann loeschen: sonst bliebe eine offene Sitzung ohne Konto zurueck.
        $this->sessions->destroyAllFor($user->id);
        $this->users->audit($actor?->userId, $actor?->displayName ?? 'System', 'user.delete', null, $user->username, ['role' => $user->role->value]);
        $this->users->delete($user->id);

        return Json::write($response, ['deleted' => true]);
    }

    /** Das Protokoll der Benutzerverwaltung, jüngste Einträge zuerst. */
    public function audit(Request $request, Response $response): Response
    {
        return Json::write($response, ['entries' => $this->users->recentAudit(50)]);
    }

    /** @return list<array<string, mixed>> */
    private static function roleCatalogue(): array
    {
        return array_map(static fn (Role $role): array => [
            'value' => $role->value,
            'label' => $role->label(),
            'permissions' => array_map(Permission::label(...), $role->permissions()),
        ], Role::cases());
    }

    private static function validateName(string $username, string $displayName): ?string
    {
        if (preg_match('/^[a-z0-9._-]{3,40}$/i', $username) !== 1) {
            return 'Der Benutzername darf 3 bis 40 Zeichen lang sein und nur Buchstaben, Ziffern, Punkt, Bindestrich und Unterstrich enthalten.';
        }

        if ($displayName === '' || mb_strlen($displayName) > 120) {
            return 'Der Anzeigename muss zwischen 1 und 120 Zeichen lang sein.';
        }

        return null;
    }

    private static function body(Request $request): array
    {
        $parsed = $request->getParsedBody();

        return is_array($parsed) ? $parsed : Json::decode((string) $request->getBody());
    }
}
