<?php

declare(strict_types=1);

namespace Ekdos\Auth;

use Ekdos\Http\Middleware\SessionMiddleware;
use Ekdos\Support\Json;
use Ekdos\Users\Permission;
use Ekdos\Users\UserRepository;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

final readonly class AuthController
{
    public function __construct(
        private AuthService $auth,
        private SessionCookie $cookie,
        private UserRepository $users,
    ) {}

    public function login(Request $request, Response $response): Response
    {
        $body = $request->getParsedBody();
        $body = is_array($body) ? $body : Json::decode((string) $request->getBody());

        $result = $this->auth->login(
            username: (string) ($body['username'] ?? $body['account'] ?? ''),
            password: (string) ($body['password'] ?? ''),
            clientIp: self::clientIp($request),
        );

        if ($result['ok'] === false) {
            return Json::error($response, $result['error'], $result['status']);
        }

        return $this->cookie->attach(Json::write($response, ['user' => self::describe($result['session'])]), $result['session']->id);
    }

    /**
     * Die Auth-Sonde der Oberflaeche.
     *
     * Bewusst ohne RequireAuth: die SPA fragt hier vor jeder Anzeige an und
     * entscheidet anhand von 200 oder 401, ob sie die Anmeldemaske zeigt.
     */
    public function me(Request $request, Response $response): Response
    {
        $session = SessionMiddleware::of($request);

        if ($session === null) {
            return Json::write($response, ['user' => null], 401);
        }

        return Json::write($response, ['user' => self::describe($session)]);
    }

    /**
     * Haelt die Sitzung am Leben, solange jemand am Bildschirm arbeitet.
     * Die Oberflaeche ruft das alle fuenf Minuten auf.
     */
    public function refresh(Request $request, Response $response): Response
    {
        $session = SessionMiddleware::of($request);

        if ($session === null) {
            return Json::write($response, ['user' => null], 401);
        }

        return Json::write($response, ['user' => self::describe($session)]);
    }

    public function logout(Request $request, Response $response): Response
    {
        $this->auth->logout($this->cookie->read($request));

        return $this->cookie->clear(Json::write($response, ['ok' => true]));
    }

    /** Eigenes Passwort aendern. Erlaubt jedem angemeldeten Benutzer. */
    public function changeOwnPassword(Request $request, Response $response): Response
    {
        $session = SessionMiddleware::of($request);

        if ($session === null) {
            return Json::error($response, 'Bitte erneut anmelden.', 401);
        }

        $body = $request->getParsedBody();
        $body = is_array($body) ? $body : Json::decode((string) $request->getBody());
        $current = (string) ($body['currentPassword'] ?? '');
        $next = (string) ($body['newPassword'] ?? '');

        $user = $this->users->findByUsernameWithHash($session->username);

        if ($user === null || !password_verify($current, $user->passwordHash)) {
            return Json::error($response, 'Das aktuelle Passwort ist nicht korrekt.', 403);
        }

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

        $this->users->update($user->id, ['password_hash' => AuthService::hash($next)]);
        $this->users->audit($user->id, $user->displayName, 'password.self', $user->id, $user->username);

        // Alle anderen Sitzungen dieses Benutzers verfallen; die eigene bleibt bestehen.
        return Json::write($response, ['ok' => true]);
    }

    /** @return array<string, mixed> */
    private static function describe(Session $session): array
    {
        return [
            'id' => $session->userId,
            'username' => $session->username,
            'name' => $session->displayName,
            'role' => $session->role->value,
            'roleLabel' => $session->role->label(),
            'permissions' => $session->role->permissions(),
            'canManageUsers' => $session->can(Permission::UsersManage),
        ];
    }

    /**
     * Hinter nginx steht die echte Adresse in X-Forwarded-For. Es wird nur der
     * erste Eintrag verwendet und nur, wenn der Header ueberhaupt gesetzt ist --
     * der Origin ist ausschliesslich ueber den Proxy erreichbar.
     */
    private static function clientIp(Request $request): string
    {
        $forwarded = $request->getHeaderLine('X-Forwarded-For');

        if ($forwarded !== '') {
            return trim(explode(',', $forwarded)[0]);
        }

        $server = $request->getServerParams();

        return is_string($server['REMOTE_ADDR'] ?? null) ? $server['REMOTE_ADDR'] : 'unbekannt';
    }
}
