<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Unit;

use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use PHPUnit\Framework\TestCase;
use ReflectionClass;

/**
 * Verify that POST/DELETE endpoints do NOT have #[NoCSRFRequired].
 *
 * GET endpoints may legitimately skip CSRF checks (standard Nextcloud practice).
 * POST/DELETE endpoints must have CSRF protection enabled (no #[NoCSRFRequired]).
 *
 * Part of Issue #171.
 */
class CsrfProtectionTest extends TestCase {

    /**
     * Map of controller class => method names that are POST/DELETE and
     * MUST NOT have #[NoCSRFRequired].
     */
    private function getPostDeleteEndpoints(): array {
        return [
            // ExportController — encrypted exports use POST
            \OCA\Mitgliederverwaltung\Controller\ExportController::class => [
                'membersEncrypted',
                'feesEncrypted',
                'birthdaysEncrypted',
                'bundleSensitive',
                'bankingEncrypted',
            ],
            // ReportController — encrypted report uses POST
            \OCA\Mitgliederverwaltung\Controller\ReportController::class => [
                'encrypted',
            ],
            // DsgvoController — export (POST) and hardDelete (DELETE)
            \OCA\Mitgliederverwaltung\Controller\DsgvoController::class => [
                'export',
                'hardDelete',
            ],
        ];
    }

    /**
     * Map of controller class => method names that are GET and
     * MAY have #[NoCSRFRequired] (read-only endpoints).
     */
    private function getGetEndpoints(): array {
        return [
            \OCA\Mitgliederverwaltung\Controller\ExportController::class => [
                'members',
                'fees',
                'birthdays',
                'bundle',
                'entityTypes',
                'entity',
            ],
            \OCA\Mitgliederverwaltung\Controller\ReportController::class => [
                'index',
                'preview',
                'pdf',
            ],
        ];
    }

    /**
     * @dataProvider postDeleteEndpointsProvider
     */
    public function testPostDeleteEndpointsRequireCsrf(string $controllerClass, string $method): void {
        $rc = new ReflectionClass($controllerClass);
        $rm = $rc->getMethod($method);

        $attrs = $rm->getAttributes(NoCSRFRequired::class);

        $this->assertEmpty(
            $attrs,
            sprintf(
                '%s::%s is a POST/DELETE endpoint but has #[NoCSRFRequired] — '
                . 'this bypasses CSRF protection on a state-changing endpoint.',
                $controllerClass,
                $method
            )
        );
    }

    /**
     * @dataProvider getEndpointsProvider
     */
    public function testGetEndpointsAllowNoCsrf(string $controllerClass, string $method): void {
        $rc = new ReflectionClass($controllerClass);
        $rm = $rc->getMethod($method);

        $attrs = $rm->getAttributes(NoCSRFRequired::class);

        $this->assertNotEmpty(
            $attrs,
            sprintf(
                '%s::%s is a GET (read-only) endpoint and should have #[NoCSRFRequired] '
                . 'for compatibility with Nextcloud API calls.',
                $controllerClass,
                $method
            )
        );
    }

    public static function postDeleteEndpointsProvider(): array {
        $data = [];
        $endpoints = (new self('dummy'))->getPostDeleteEndpoints();
        foreach ($endpoints as $class => $methods) {
            foreach ($methods as $method) {
                $shortClass = (new ReflectionClass($class))->getShortName();
                $data["$shortClass::$method"] = [$class, $method];
            }
        }
        return $data;
    }

    public static function getEndpointsProvider(): array {
        $data = [];
        $endpoints = (new self('dummy'))->getGetEndpoints();
        foreach ($endpoints as $class => $methods) {
            foreach ($methods as $method) {
                $shortClass = (new ReflectionClass($class))->getShortName();
                $data["$shortClass::$method"] = [$class, $method];
            }
        }
        return $data;
    }
}
