<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Integration;

use OCA\Mitgliederverwaltung\Controller\ReportController;
use OCA\Mitgliederverwaltung\Service\EncryptedExportService;
use OCA\Mitgliederverwaltung\Service\InventoryReportService;
use OCA\Mitgliederverwaltung\Service\PdfService;
use OCA\Mitgliederverwaltung\Service\PermissionService;
use OCA\Mitgliederverwaltung\Service\ReportService;
use OCA\Mitgliederverwaltung\Service\ValidationException;
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;

/**
 * Integration-level tests for ReportController API endpoints.
 */
class ReportControllerTest extends TestCase {

    private ReportController $controller;
    private ReportService&MockObject $reportService;
    private InventoryReportService&MockObject $inventoryReportService;
    private PdfService&MockObject $pdfService;
    private EncryptedExportService&MockObject $encryptedService;
    private PermissionService&MockObject $permissionService;
    private IUserSession&MockObject $userSession;
    private IRequest&MockObject $request;
    private LoggerInterface&MockObject $logger;

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

        $this->reportService = $this->createMock(ReportService::class);
        $this->inventoryReportService = $this->createMock(InventoryReportService::class);
        $this->pdfService = $this->createMock(PdfService::class);
        $this->encryptedService = $this->createMock(EncryptedExportService::class);
        $this->permissionService = $this->createMock(PermissionService::class);
        $this->userSession = $this->createMock(IUserSession::class);
        $this->request = $this->createMock(IRequest::class);
        $this->logger = $this->createMock(LoggerInterface::class);

        // Default: authenticated user
        $user = $this->createMock(IUser::class);
        $user->method('getUID')->willReturn('testuser');
        $this->userSession->method('getUser')->willReturn($user);

        $this->controller = new ReportController(
            'mitgliederverwaltung',
            $this->request,
            $this->reportService,
            $this->inventoryReportService,
            $this->pdfService,
            $this->encryptedService,
            $this->permissionService,
            $this->userSession,
            $this->logger
        );
    }

    private function sampleReportData(): array {
        return [
            'title' => 'Mitgliederliste',
            'headers' => ['Name', 'Vorname'],
            'rows' => [['Mustermann', 'Max']],
        ];
    }

    // ── GET /api/v1/reports (index) ────────────────────────────────

    public function testIndexReturns200WithReportList(): void {
        $response = $this->controller->index();

        $this->assertSame(Http::STATUS_OK, $response->getStatus());
        $data = $response->getData();
        $this->assertArrayHasKey('reports', $data);
        $this->assertIsArray($data['reports']);
        $this->assertGreaterThan(0, count($data['reports']));
    }

    public function testIndexContainsExpectedReportTypes(): void {
        $response = $this->controller->index();

        $ids = array_column($response->getData()['reports'], 'id');
        $this->assertContains('mitgliederliste', $ids);
        $this->assertContains('beitragsliste', $ids);
        $this->assertContains('bankverbindungen', $ids);
    }

    // ── GET /api/v1/reports/{type}/preview ─────────────────────────

    public function testPreviewReturns200ForMitgliederliste(): void {
        $this->request->method('getParam')->willReturn(null);
        $this->reportService->method('generateMitgliederliste')
            ->willReturn($this->sampleReportData());

        $response = $this->controller->preview('mitgliederliste');

        $this->assertSame(Http::STATUS_OK, $response->getStatus());
    }

    public function testPreviewReturns400ForUnknownType(): void {
        $this->request->method('getParam')->willReturn(null);

        $response = $this->controller->preview('nonexistent');

        $this->assertSame(Http::STATUS_BAD_REQUEST, $response->getStatus());
    }

    public function testPreviewReturns403ForBankverbindungenWithoutPermission(): void {
        $this->request->method('getParam')->willReturn(null);
        $this->permissionService->method('canSeeBanking')
            ->with('testuser')
            ->willReturn(false);

        $response = $this->controller->preview('bankverbindungen');

        $this->assertSame(Http::STATUS_FORBIDDEN, $response->getStatus());
    }

    public function testPreviewReturns200ForBankverbindungenWithPermission(): void {
        $this->request->method('getParam')->willReturn(null);
        $this->permissionService->method('canSeeBanking')
            ->with('testuser')
            ->willReturn(true);
        $this->reportService->method('generateBankverbindungen')
            ->willReturn($this->sampleReportData());

        $response = $this->controller->preview('bankverbindungen');

        $this->assertSame(Http::STATUS_OK, $response->getStatus());
    }

    public function testPreviewReturns500OnException(): void {
        $this->request->method('getParam')->willReturn(null);
        $this->reportService->method('generateMitgliederliste')
            ->willThrowException(new \RuntimeException('fail'));

        $response = $this->controller->preview('mitgliederliste');

        $this->assertSame(Http::STATUS_INTERNAL_SERVER_ERROR, $response->getStatus());
    }

    public function testPreviewStufenlisteReturns200(): void {
        $this->request->method('getParam')->willReturn(null);
        $this->reportService->method('generateStufenliste')
            ->willReturn($this->sampleReportData());

        $response = $this->controller->preview('stufenliste');

        $this->assertSame(Http::STATUS_OK, $response->getStatus());
    }

    public function testPreviewAllergielisteReturns200(): void {
        $this->request->method('getParam')->willReturn(null);
        $this->reportService->method('generateAllergieliste')
            ->willReturn($this->sampleReportData());

        $response = $this->controller->preview('allergieliste');

        $this->assertSame(Http::STATUS_OK, $response->getStatus());
    }

    public function testPreviewFamilienlisteReturns200(): void {
        $this->request->method('getParam')->willReturn(null);
        $this->reportService->method('generateFamilienliste')
            ->willReturn($this->sampleReportData());

        $response = $this->controller->preview('familienliste');

        $this->assertSame(Http::STATUS_OK, $response->getStatus());
    }

    // ── GET /api/v1/reports/{type}/pdf ─────────────────────────────

    public function testPdfReturnsDownloadForValidType(): void {
        $this->request->method('getParam')->willReturn(null);
        $this->reportService->method('generateMitgliederliste')
            ->willReturn($this->sampleReportData());
        $this->pdfService->method('generateReport')
            ->willReturn(['content' => 'pdfdata', 'filename' => 'report.pdf']);

        $response = $this->controller->pdf('mitgliederliste');

        $this->assertSame(Http::STATUS_OK, $response->getStatus());
    }

    public function testPdfReturns400ForUnknownType(): void {
        $this->request->method('getParam')->willReturn(null);

        $response = $this->controller->pdf('nonexistent');

        $this->assertSame(Http::STATUS_BAD_REQUEST, $response->getStatus());
    }

    public function testPdfReturns403ForBankverbindungenWithoutPermission(): void {
        $this->request->method('getParam')->willReturn(null);
        $this->permissionService->method('canSeeBanking')->willReturn(false);

        $response = $this->controller->pdf('bankverbindungen');

        $this->assertSame(Http::STATUS_FORBIDDEN, $response->getStatus());
    }

    public function testPdfReturns500OnException(): void {
        $this->request->method('getParam')->willReturn(null);
        $this->reportService->method('generateMitgliederliste')
            ->willThrowException(new \RuntimeException('fail'));

        $response = $this->controller->pdf('mitgliederliste');

        $this->assertSame(Http::STATUS_INTERNAL_SERVER_ERROR, $response->getStatus());
    }

    // ── POST /api/v1/reports/{type}/encrypted ──────────────────────

    public function testEncryptedReturns400WhenPasswordMissing(): void {
        $this->request->method('getParam')->willReturn(null);

        $response = $this->controller->encrypted('mitgliederliste');

        $this->assertSame(Http::STATUS_BAD_REQUEST, $response->getStatus());
    }

    public function testEncryptedReturns400ForUnknownType(): void {
        $this->request->method('getParam')
            ->willReturnCallback(fn($name) => match ($name) {
                'password' => 'secret',
                default => null,
            });

        $response = $this->controller->encrypted('nonexistent');

        $this->assertSame(Http::STATUS_BAD_REQUEST, $response->getStatus());
    }

    public function testEncryptedReturnsDownloadOnSuccess(): void {
        $this->request->method('getParam')
            ->willReturnCallback(fn($name) => match ($name) {
                'password' => 'secret',
                default => null,
            });

        $this->reportService->method('generateMitgliederliste')
            ->willReturn($this->sampleReportData());
        $this->pdfService->method('generateReport')
            ->willReturn(['content' => 'pdfdata', 'filename' => 'report.pdf']);
        $this->encryptedService->method('createEncryptedExport')
            ->willReturn(['content' => 'zip', 'filename' => 'report.zip']);

        $response = $this->controller->encrypted('mitgliederliste');

        $this->assertSame(Http::STATUS_OK, $response->getStatus());
    }

    public function testEncryptedReturns403ForBankverbindungenWithoutPermission(): void {
        $this->request->method('getParam')
            ->willReturnCallback(fn($name) => match ($name) {
                'password' => 'secret',
                default => null,
            });
        $this->permissionService->method('canSeeBanking')->willReturn(false);

        $response = $this->controller->encrypted('bankverbindungen');

        $this->assertSame(Http::STATUS_FORBIDDEN, $response->getStatus());
    }

    public function testEncryptedReturns500OnException(): void {
        $this->request->method('getParam')
            ->willReturnCallback(fn($name) => match ($name) {
                'password' => 'secret',
                default => null,
            });
        $this->reportService->method('generateMitgliederliste')
            ->willThrowException(new \RuntimeException('fail'));

        $response = $this->controller->encrypted('mitgliederliste');

        $this->assertSame(Http::STATUS_INTERNAL_SERVER_ERROR, $response->getStatus());
    }
}
