<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Unit;

use OCA\Mitgliederverwaltung\Controller\FamilyController;
use OCA\Mitgliederverwaltung\Controller\ImportController;
use OCA\Mitgliederverwaltung\Controller\MemberController;
use OCA\Mitgliederverwaltung\Controller\PageController;
use OCA\Mitgliederverwaltung\Controller\FeeController;
use OCA\Mitgliederverwaltung\Middleware\AuthorizationMiddleware;
use OCA\Mitgliederverwaltung\Service\PermissionService;
use OCP\AppFramework\Http;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserSession;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;

class AuthorizationMiddlewareTest extends TestCase {

    private PermissionService&MockObject $permissionService;
    private IUserSession&MockObject $userSession;
    private IRequest&MockObject $request;
    private LoggerInterface&MockObject $logger;
    private IUser&MockObject $user;

    protected function setUp(): void {
        parent::setUp();

        $this->permissionService = $this->createMock(PermissionService::class);
        $this->userSession = $this->createMock(IUserSession::class);
        $this->request = $this->createMock(IRequest::class);
        $this->logger = $this->createMock(LoggerInterface::class);
        $this->user = $this->createMock(IUser::class);
        $this->user->method('getUID')->willReturn('testuser');
    }

    private function createMiddleware(): AuthorizationMiddleware {
        return new AuthorizationMiddleware(
            $this->permissionService,
            $this->userSession,
            $this->request,
            $this->logger
        );
    }

    // -- PageController is always allowed --

    public function testPageControllerSkipsAuthCheck(): void {
        $middleware = $this->createMiddleware();
        $this->userSession->method('getUser')->willReturn($this->user);
        $controller = $this->createMock(PageController::class);

        // Should not throw
        $middleware->beforeController($controller, 'index');
        $this->assertTrue(true);
    }

    // -- No user session skips auth --

    public function testNoUserSessionSkipsAuth(): void {
        $middleware = $this->createMiddleware();
        $this->userSession->method('getUser')->willReturn(null);
        $controller = $this->createMock(MemberController::class);

        // Should not throw
        $middleware->beforeController($controller, 'index');
        $this->assertTrue(true);
    }

    // -- Admin-only controllers --

    public function testImportControllerRequiresAdmin(): void {
        $middleware = $this->createMiddleware();
        $this->userSession->method('getUser')->willReturn($this->user);
        $this->permissionService->method('isAdmin')->with('testuser')->willReturn(false);

        $controller = $this->createMock(ImportController::class);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Authorization: admin required');
        $middleware->beforeController($controller, 'upload');
    }

    public function testImportControllerAllowsAdmin(): void {
        $middleware = $this->createMiddleware();
        $this->userSession->method('getUser')->willReturn($this->user);
        $this->permissionService->method('isAdmin')->with('testuser')->willReturn(true);

        $controller = $this->createMock(ImportController::class);

        $middleware->beforeController($controller, 'upload');
        $this->assertTrue(true);
    }

    // -- Admin-only methods --

    public function testFeeCreateRuleRequiresAdmin(): void {
        $middleware = $this->createMiddleware();
        $this->userSession->method('getUser')->willReturn($this->user);
        $this->permissionService->method('isAdmin')->with('testuser')->willReturn(false);

        $controller = $this->createMock(FeeController::class);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Authorization: admin required');
        $middleware->beforeController($controller, 'createRule');
    }

    public function testFeeListRulesAllowsReadUser(): void {
        $middleware = $this->createMiddleware();
        $this->userSession->method('getUser')->willReturn($this->user);
        $this->request->method('getMethod')->willReturn('GET');
        $this->permissionService->method('canRead')->with('testuser')->willReturn(true);

        $controller = $this->createMock(FeeController::class);

        $middleware->beforeController($controller, 'listRules');
        $this->assertTrue(true);
    }

    // -- Read access --

    public function testGetMembersRequiresReadAccess(): void {
        $middleware = $this->createMiddleware();
        $this->userSession->method('getUser')->willReturn($this->user);
        $this->request->method('getMethod')->willReturn('GET');
        $this->permissionService->method('canRead')->with('testuser')->willReturn(false);

        $controller = $this->createMock(MemberController::class);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Authorization: read access required');
        $middleware->beforeController($controller, 'index');
    }

    public function testGetMembersAllowsReadUser(): void {
        $middleware = $this->createMiddleware();
        $this->userSession->method('getUser')->willReturn($this->user);
        $this->request->method('getMethod')->willReturn('GET');
        $this->permissionService->method('canRead')->with('testuser')->willReturn(true);

        $controller = $this->createMock(MemberController::class);

        $middleware->beforeController($controller, 'index');
        $this->assertTrue(true);
    }

    // -- Write access --

    public function testCreateMemberRequiresWriteAccess(): void {
        $middleware = $this->createMiddleware();
        $this->userSession->method('getUser')->willReturn($this->user);
        $this->request->method('getMethod')->willReturn('POST');
        $this->permissionService->method('canWrite')->with('testuser')->willReturn(false);

        $controller = $this->createMock(MemberController::class);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Authorization: write access required');
        $middleware->beforeController($controller, 'create');
    }

    public function testCreateMemberAllowsWriteUser(): void {
        $middleware = $this->createMiddleware();
        $this->userSession->method('getUser')->willReturn($this->user);
        $this->request->method('getMethod')->willReturn('POST');
        $this->permissionService->method('canWrite')->with('testuser')->willReturn(true);

        $controller = $this->createMock(MemberController::class);

        $middleware->beforeController($controller, 'create');
        $this->assertTrue(true);
    }

    public function testDeleteFamilyRequiresWriteAccess(): void {
        $middleware = $this->createMiddleware();
        $this->userSession->method('getUser')->willReturn($this->user);
        $this->request->method('getMethod')->willReturn('DELETE');
        $this->permissionService->method('canWrite')->with('testuser')->willReturn(false);

        $controller = $this->createMock(FamilyController::class);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Authorization: write access required');
        $middleware->beforeController($controller, 'destroy');
    }

    // -- afterException --

    public function testAfterExceptionReturns403ForAuthError(): void {
        $middleware = $this->createMiddleware();
        $exception = new \RuntimeException('Authorization: admin required');

        $response = $middleware->afterException(
            $this->createMock(MemberController::class),
            'create',
            $exception
        );

        $this->assertSame(Http::STATUS_FORBIDDEN, $response->getStatus());
        $data = $response->getData();
        $this->assertStringContainsString('Keine Berechtigung', $data['error']);
    }

    public function testAfterExceptionRethrowsOtherExceptions(): void {
        $middleware = $this->createMiddleware();
        $exception = new \RuntimeException('Something else');

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Something else');

        $middleware->afterException(
            $this->createMock(MemberController::class),
            'create',
            $exception
        );
    }
}
