<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Middleware;

use OCA\Mitgliederverwaltung\Controller\AuditController;
use OCA\Mitgliederverwaltung\Controller\BackupController;
use OCA\Mitgliederverwaltung\Controller\DsgvoController;
use OCA\Mitgliederverwaltung\Controller\ExportController;
use OCA\Mitgliederverwaltung\Controller\FeeController;
use OCA\Mitgliederverwaltung\Controller\FileController;
use OCA\Mitgliederverwaltung\Controller\ImportController;
use OCA\Mitgliederverwaltung\Controller\MemberController;
use OCA\Mitgliederverwaltung\Controller\MilestoneController;
use OCA\Mitgliederverwaltung\Controller\PageController;
use OCA\Mitgliederverwaltung\Controller\PermissionController;
use OCA\Mitgliederverwaltung\Controller\StufeController;
use OCA\Mitgliederverwaltung\Service\PermissionService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Middleware;
use OCP\IRequest;
use OCP\IUserSession;
use Psr\Log\LoggerInterface;

/**
 * Authorization middleware that enforces permission checks on all API endpoints.
 *
 * Permission levels:
 * - PageController: no check (serves the SPA shell)
 * - Admin-only controllers (Import, Permission, Audit, DSGVO): requires 'admin'
 * - Admin-only actions (fee rules, stufe CRUD, export sensitive): requires 'admin'
 * - Write actions (POST/PUT/DELETE): requires 'write' or higher
 * - Read actions (GET): requires 'read' or higher
 *
 * Part of Issue #166.
 */
class AuthorizationMiddleware extends Middleware {

    /** Admin-only method lists for specific controllers */
    private const ADMIN_METHODS_FEE = ['createRule', 'updateRule', 'batchCalculate'];
    private const ADMIN_METHODS_STUFE = ['create', 'update', 'destroy'];
    private const ADMIN_METHODS_EXPORT = ['bundleSensitive'];
    private const ADMIN_METHODS_MILESTONE = ['updateSettings'];
    private const ADMIN_METHODS_FILE = ['updateSettings'];
    private const ADMIN_METHODS_MEMBER = ['revealAllergies', 'archive'];

    /** Methods that are read-only regardless of HTTP verb */
    private const READ_METHODS = [
        'index', 'show', 'search', 'preview', 'listRules', 'listRecords',
        'memberRecords', 'memberHistory', 'archive', 'fields', 'entityTypes',
        'getSettings', 'memberFiles', 'lagerFiles',
    ];

    private PermissionService $permissionService;
    private IUserSession $userSession;
    private IRequest $request;
    private LoggerInterface $logger;

    public function __construct(
        PermissionService $permissionService,
        IUserSession $userSession,
        IRequest $request,
        LoggerInterface $logger
    ) {
        $this->permissionService = $permissionService;
        $this->userSession = $userSession;
        $this->request = $request;
        $this->logger = $logger;
    }

    /**
     * Check authorization before controller action executes.
     */
    public function beforeController($controller, $methodName): void {
        // Skip PageController — it serves the SPA shell
        if ($controller instanceof PageController) {
            return;
        }

        $user = $this->userSession->getUser();
        if ($user === null) {
            // Not authenticated — Nextcloud handles this via its own middleware
            return;
        }

        $userId = $user->getUID();

        // 1. Check admin-only controllers
        if ($controller instanceof ImportController
            || $controller instanceof PermissionController
            || $controller instanceof AuditController
            || $controller instanceof DsgvoController
            || $controller instanceof BackupController
        ) {
            if (!$this->permissionService->isAdmin($userId)) {
                throw new \RuntimeException('Authorization: admin required');
            }
            return;
        }

        // 2. Check admin-only methods on specific controllers
        $adminMethods = $this->getAdminMethods($controller);
        if ($adminMethods !== null && in_array($methodName, $adminMethods, true)) {
            if (!$this->permissionService->isAdmin($userId)) {
                throw new \RuntimeException('Authorization: admin required');
            }
            return;
        }

        // 3. Determine if this is a read or write operation
        $isReadOnly = in_array($methodName, self::READ_METHODS, true)
            || $this->request->getMethod() === 'GET';

        if ($isReadOnly) {
            // Read access required
            if (!$this->permissionService->canRead($userId)) {
                throw new \RuntimeException('Authorization: read access required');
            }
        } else {
            // Write access required
            if (!$this->permissionService->canWrite($userId)) {
                throw new \RuntimeException('Authorization: write access required');
            }
        }
    }

    /**
     * Get admin-only method list for a controller, or null if none.
     *
     * @return string[]|null
     */
    private function getAdminMethods($controller): ?array {
        if ($controller instanceof FeeController) {
            return self::ADMIN_METHODS_FEE;
        }
        if ($controller instanceof StufeController) {
            return self::ADMIN_METHODS_STUFE;
        }
        if ($controller instanceof ExportController) {
            return self::ADMIN_METHODS_EXPORT;
        }
        if ($controller instanceof MilestoneController) {
            return self::ADMIN_METHODS_MILESTONE;
        }
        if ($controller instanceof FileController) {
            return self::ADMIN_METHODS_FILE;
        }
        if ($controller instanceof MemberController) {
            return self::ADMIN_METHODS_MEMBER;
        }
        return null;
    }

    /**
     * Convert authorization exceptions to proper HTTP 403 responses.
     */
    public function afterException($controller, $methodName, \Exception $exception): JSONResponse {
        if (str_starts_with($exception->getMessage(), 'Authorization:')) {
            $this->logger->warning('Access denied', [
                'controller' => get_class($controller),
                'method' => $methodName,
                'message' => $exception->getMessage(),
                'app' => 'mitgliederverwaltung',
            ]);

            return new JSONResponse(
                ['error' => 'Keine Berechtigung fuer diese Aktion.'],
                Http::STATUS_FORBIDDEN
            );
        }

        throw $exception;
    }
}
