<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Unit;

use OCA\Mitgliederverwaltung\Db\Member;
use OCA\Mitgliederverwaltung\Db\MemberMapper;
use OCA\Mitgliederverwaltung\Db\Permission;
use OCA\Mitgliederverwaltung\Db\PermissionMapper;
use OCA\Mitgliederverwaltung\Service\PermissionService;
use OCA\Mitgliederverwaltung\Service\ValidationException;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\IGroupManager;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class PermissionServiceTest extends TestCase {

    private PermissionService $service;
    private PermissionMapper&MockObject $permissionMapper;
    private MemberMapper&MockObject $memberMapper;
    private IGroupManager&MockObject $groupManager;

    protected function setUp(): void {
        $this->permissionMapper = $this->createMock(PermissionMapper::class);
        $this->memberMapper = $this->createMock(MemberMapper::class);
        $this->groupManager = $this->createMock(IGroupManager::class);
        // By default, NC admin group check returns false so existing tests pass unchanged
        $this->groupManager->method('isInGroup')->willReturn(false);

        $this->service = new PermissionService(
            $this->permissionMapper,
            $this->memberMapper,
            $this->groupManager
        );
    }

    private function createPermission(string $level, ?string $stufenJson = null, bool $canSeeBanking = false): Permission {
        $perm = new Permission();
        $perm->setNcUserId('user1');
        $perm->setLevel($level);
        $perm->setAllowedStufenJson($stufenJson);
        $perm->setCanSeeBanking($canSeeBanking);
        $ref = new \ReflectionProperty($perm, 'id');
        $ref->setAccessible(true);
        $ref->setValue($perm, 1);
        return $perm;
    }

    // ── getUserPermission() ────────────────────────────────────────

    public function testGetUserPermissionReturnsPermission(): void {
        $perm = $this->createPermission('admin');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $result = $this->service->getUserPermission('user1');

        $this->assertInstanceOf(Permission::class, $result);
        $this->assertEquals('admin', $result->getLevel());
    }

    public function testGetUserPermissionReturnsNullWhenNotFound(): void {
        $this->permissionMapper->method('findByUserId')
            ->willThrowException(new DoesNotExistException(''));

        $result = $this->service->getUserPermission('unknown');

        $this->assertNull($result);
    }

    // ── canAccess() ────────────────────────────────────────────────

    public function testCanAccessReturnsTrueForAdmin(): void {
        $perm = $this->createPermission('admin');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertTrue($this->service->canAccess('user1'));
    }

    public function testCanAccessReturnsTrueForRead(): void {
        $perm = $this->createPermission('read');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertTrue($this->service->canAccess('user1'));
    }

    public function testCanAccessReturnsFalseForNone(): void {
        $perm = $this->createPermission('none');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertFalse($this->service->canAccess('user1'));
    }

    public function testCanAccessReturnsFalseForUnknownUser(): void {
        $this->permissionMapper->method('findByUserId')
            ->willThrowException(new DoesNotExistException(''));

        $this->assertFalse($this->service->canAccess('unknown'));
    }

    // ── canRead() ──────────────────────────────────────────────────

    public function testCanReadReturnsTrueForAdmin(): void {
        $perm = $this->createPermission('admin');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertTrue($this->service->canRead('user1'));
    }

    public function testCanReadReturnsTrueForFull(): void {
        $perm = $this->createPermission('full');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertTrue($this->service->canRead('user1'));
    }

    public function testCanReadReturnsTrueForStufe(): void {
        $perm = $this->createPermission('stufe');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertTrue($this->service->canRead('user1'));
    }

    public function testCanReadReturnsTrueForRead(): void {
        $perm = $this->createPermission('read');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertTrue($this->service->canRead('user1'));
    }

    public function testCanReadReturnsFalseForNone(): void {
        $perm = $this->createPermission('none');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertFalse($this->service->canRead('user1'));
    }

    public function testCanReadReturnsFalseForUnknown(): void {
        $this->permissionMapper->method('findByUserId')
            ->willThrowException(new DoesNotExistException(''));

        $this->assertFalse($this->service->canRead('unknown'));
    }

    // ── canWrite() ─────────────────────────────────────────────────

    public function testCanWriteReturnsTrueForAdmin(): void {
        $perm = $this->createPermission('admin');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertTrue($this->service->canWrite('user1'));
    }

    public function testCanWriteReturnsTrueForFull(): void {
        $perm = $this->createPermission('full');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertTrue($this->service->canWrite('user1'));
    }

    public function testCanWriteReturnsFalseForRead(): void {
        $perm = $this->createPermission('read');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertFalse($this->service->canWrite('user1'));
    }

    public function testCanWriteReturnsFalseForNone(): void {
        $perm = $this->createPermission('none');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertFalse($this->service->canWrite('user1'));
    }

    public function testCanWriteStufeAllowedMember(): void {
        $perm = $this->createPermission('stufe', '[1, 2]');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $member = new Member();
        $member->setStufeId(1);
        $ref = new \ReflectionProperty($member, 'id');
        $ref->setAccessible(true);
        $ref->setValue($member, 10);

        $this->memberMapper->method('findById')->willReturn($member);

        $this->assertTrue($this->service->canWrite('user1', 10));
    }

    public function testCanWriteStufeDisallowedMember(): void {
        $perm = $this->createPermission('stufe', '[1, 2]');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $member = new Member();
        $member->setStufeId(5);
        $ref = new \ReflectionProperty($member, 'id');
        $ref->setAccessible(true);
        $ref->setValue($member, 10);

        $this->memberMapper->method('findById')->willReturn($member);

        $this->assertFalse($this->service->canWrite('user1', 10));
    }

    public function testCanWriteStufeWithoutMemberId(): void {
        $perm = $this->createPermission('stufe', '[1, 2]');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertFalse($this->service->canWrite('user1'));
    }

    public function testCanWriteStufeWithDeletedMember(): void {
        $perm = $this->createPermission('stufe', '[1, 2]');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);
        $this->memberMapper->method('findById')
            ->willThrowException(new DoesNotExistException(''));

        $this->assertFalse($this->service->canWrite('user1', 999));
    }

    // ── canWriteStufe() ────────────────────────────────────────────

    public function testCanWriteStufeReturnsTrueForAdmin(): void {
        $perm = $this->createPermission('admin');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertTrue($this->service->canWriteStufe('user1', 5));
    }

    public function testCanWriteStufeReturnsTrueForFull(): void {
        $perm = $this->createPermission('full');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertTrue($this->service->canWriteStufe('user1', 5));
    }

    public function testCanWriteStufeReturnsTrueForAllowedStufe(): void {
        $perm = $this->createPermission('stufe', '[1, 5]');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertTrue($this->service->canWriteStufe('user1', 5));
    }

    public function testCanWriteStufeReturnsFalseForDisallowedStufe(): void {
        $perm = $this->createPermission('stufe', '[1, 2]');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertFalse($this->service->canWriteStufe('user1', 5));
    }

    public function testCanWriteStufeReturnsFalseForRead(): void {
        $perm = $this->createPermission('read');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertFalse($this->service->canWriteStufe('user1', 5));
    }

    // ── isAdmin() ──────────────────────────────────────────────────

    public function testIsAdminReturnsTrueForAdmin(): void {
        $perm = $this->createPermission('admin');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertTrue($this->service->isAdmin('user1'));
    }

    public function testIsAdminReturnsFalseForFull(): void {
        $perm = $this->createPermission('full');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertFalse($this->service->isAdmin('user1'));
    }

    public function testIsAdminReturnsFalseForUnknown(): void {
        $this->permissionMapper->method('findByUserId')
            ->willThrowException(new DoesNotExistException(''));

        $this->assertFalse($this->service->isAdmin('unknown'));
    }

    // ── canSeeBanking() ────────────────────────────────────────────

    public function testCanSeeBankingReturnsTrue(): void {
        $perm = $this->createPermission('full', null, true);
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertTrue($this->service->canSeeBanking('user1'));
    }

    public function testCanSeeBankingReturnsFalse(): void {
        $perm = $this->createPermission('full', null, false);
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $this->assertFalse($this->service->canSeeBanking('user1'));
    }

    public function testCanSeeBankingReturnsFalseForUnknown(): void {
        $this->permissionMapper->method('findByUserId')
            ->willThrowException(new DoesNotExistException(''));

        $this->assertFalse($this->service->canSeeBanking('unknown'));
    }

    // ── getVisibleStufen() ─────────────────────────────────────────

    public function testGetVisibleStufenReturnsNullForAdmin(): void {
        $perm = $this->createPermission('admin');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $result = $this->service->getVisibleStufen('user1');

        $this->assertNull($result);
    }

    public function testGetVisibleStufenReturnsNullForFull(): void {
        $perm = $this->createPermission('full');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $result = $this->service->getVisibleStufen('user1');

        $this->assertNull($result);
    }

    public function testGetVisibleStufenReturnsNullForRead(): void {
        $perm = $this->createPermission('read');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $result = $this->service->getVisibleStufen('user1');

        $this->assertNull($result);
    }

    public function testGetVisibleStufenReturnsIdsForStufe(): void {
        $perm = $this->createPermission('stufe', '[1, 3]');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $result = $this->service->getVisibleStufen('user1');

        $this->assertEquals([1, 3], $result);
    }

    public function testGetVisibleStufenReturnsEmptyForNone(): void {
        $perm = $this->createPermission('none');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);

        $result = $this->service->getVisibleStufen('user1');

        $this->assertEquals([], $result);
    }

    public function testGetVisibleStufenReturnsEmptyForUnknown(): void {
        $this->permissionMapper->method('findByUserId')
            ->willThrowException(new DoesNotExistException(''));

        $result = $this->service->getVisibleStufen('unknown');

        $this->assertEquals([], $result);
    }

    // ── getAllPermissions() ─────────────────────────────────────────

    public function testGetAllPermissions(): void {
        $perm = $this->createPermission('admin');
        $this->permissionMapper->method('findAll')->willReturn([$perm]);

        $result = $this->service->getAllPermissions();

        $this->assertCount(1, $result);
    }

    // ── setPermission() ────────────────────────────────────────────

    public function testSetPermissionCreatesNew(): void {
        $this->permissionMapper->method('findByUserId')
            ->willThrowException(new DoesNotExistException(''));

        $perm = $this->createPermission('admin');
        $this->permissionMapper->method('insert')->willReturn($perm);

        $result = $this->service->setPermission('user1', 'admin');

        $this->assertInstanceOf(Permission::class, $result);
    }

    public function testSetPermissionUpdatesExisting(): void {
        $existing = $this->createPermission('read');
        $this->permissionMapper->method('findByUserId')->willReturn($existing);

        $updated = $this->createPermission('admin');
        $this->permissionMapper->method('update')->willReturn($updated);

        $result = $this->service->setPermission('user1', 'admin');

        $this->assertInstanceOf(Permission::class, $result);
    }

    public function testSetPermissionWithStufen(): void {
        $this->permissionMapper->method('findByUserId')
            ->willThrowException(new DoesNotExistException(''));

        $perm = $this->createPermission('stufe', '[1, 2]');
        $this->permissionMapper->method('insert')->willReturn($perm);

        $result = $this->service->setPermission('user1', 'stufe', [1, 2]);

        $this->assertInstanceOf(Permission::class, $result);
    }

    public function testSetPermissionThrowsOnInvalidLevel(): void {
        $this->expectException(ValidationException::class);
        $this->expectExceptionMessage('Ungueltiges Berechtigungslevel');

        $this->service->setPermission('user1', 'superadmin');
    }

    // ── removePermission() ─────────────────────────────────────────

    public function testRemovePermission(): void {
        $perm = $this->createPermission('admin');
        $this->permissionMapper->method('findByUserId')->willReturn($perm);
        $this->permissionMapper->expects($this->once())->method('delete');

        $this->service->removePermission('user1');
    }

    public function testRemovePermissionNoOpWhenNotFound(): void {
        $this->permissionMapper->method('findByUserId')
            ->willThrowException(new DoesNotExistException(''));

        // Should not throw
        $this->service->removePermission('unknown');
        $this->assertTrue(true);
    }
}
