<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Integration;

use OCA\Mitgliederverwaltung\Controller\ExportController;
use OCA\Mitgliederverwaltung\Service\CsvExportService;
use OCA\Mitgliederverwaltung\Service\EncryptedExportService;
use OCA\Mitgliederverwaltung\Service\EntityExportService;
use OCA\Mitgliederverwaltung\Service\ValidationException;
use OCP\AppFramework\Http;
use OCP\IRequest;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;

/**
 * Integration-level tests for ExportController API endpoints.
 */
class ExportControllerTest extends TestCase {

    private ExportController $controller;
    private CsvExportService&MockObject $csvService;
    private EncryptedExportService&MockObject $encryptedService;
    private EntityExportService&MockObject $entityExportService;
    private IRequest&MockObject $request;
    private LoggerInterface&MockObject $logger;

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

        $this->csvService = $this->createMock(CsvExportService::class);
        $this->encryptedService = $this->createMock(EncryptedExportService::class);
        $this->entityExportService = $this->createMock(EntityExportService::class);
        $this->request = $this->createMock(IRequest::class);
        $this->logger = $this->createMock(LoggerInterface::class);

        $this->controller = new ExportController(
            'mitgliederverwaltung',
            $this->request,
            $this->csvService,
            $this->encryptedService,
            $this->entityExportService,
            $this->logger
        );
    }

    // ── GET /api/v1/export/members ─────────────────────────────────

    public function testMembersReturnsDownloadResponse(): void {
        $this->request->method('getParam')->willReturn(null);
        $this->csvService->method('exportMemberList')
            ->willReturn(['content' => 'csv-data', 'filename' => 'members.csv']);

        $response = $this->controller->members();

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

    public function testMembersReturns500OnException(): void {
        $this->request->method('getParam')->willReturn(null);
        $this->csvService->method('exportMemberList')
            ->willThrowException(new \RuntimeException('fail'));

        $response = $this->controller->members();

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

    // ── GET /api/v1/export/fees ────────────────────────────────────

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

        $response = $this->controller->fees();

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

    public function testFeesReturnsDownloadResponse(): void {
        $this->request->method('getParam')
            ->willReturnCallback(fn($name) => match ($name) {
                'year' => '2026',
                default => null,
            });
        $this->csvService->method('exportFeeRecords')
            ->willReturn(['content' => 'csv-data', 'filename' => 'fees.csv']);

        $response = $this->controller->fees();

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

    public function testFeesReturns500OnException(): void {
        $this->request->method('getParam')
            ->willReturnCallback(fn($name) => match ($name) {
                'year' => '2026',
                default => null,
            });
        $this->csvService->method('exportFeeRecords')
            ->willThrowException(new \RuntimeException('fail'));

        $response = $this->controller->fees();

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

    // ── GET /api/v1/export/birthdays ───────────────────────────────

    public function testBirthdaysReturnsDownloadResponse(): void {
        $this->request->method('getParam')->willReturn(null);
        $this->csvService->method('exportBirthdayList')
            ->willReturn(['content' => 'csv-data', 'filename' => 'birthdays.csv']);

        $response = $this->controller->birthdays();

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

    public function testBirthdaysWithMonth(): void {
        $this->request->method('getParam')
            ->willReturnCallback(fn($name) => match ($name) {
                'month' => '6',
                default => null,
            });
        $this->csvService->method('exportBirthdayList')
            ->with(6)
            ->willReturn(['content' => 'csv-data', 'filename' => 'birthdays.csv']);

        $response = $this->controller->birthdays();

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

    public function testBirthdaysReturns500OnException(): void {
        $this->request->method('getParam')->willReturn(null);
        $this->csvService->method('exportBirthdayList')
            ->willThrowException(new \RuntimeException('fail'));

        $response = $this->controller->birthdays();

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

    // ── POST /api/v1/export/members/encrypted ──────────────────────

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

        $response = $this->controller->membersEncrypted();

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

    public function testMembersEncryptedReturnsDownload(): void {
        $this->request->method('getParam')
            ->willReturnCallback(fn($name) => match ($name) {
                'password' => 'secret',
                default => null,
            });
        $this->csvService->method('exportMemberList')
            ->willReturn(['content' => 'csv', 'filename' => 'members.csv']);
        $this->encryptedService->method('createEncryptedExport')
            ->willReturn(['content' => 'zip', 'filename' => 'export.zip']);

        $response = $this->controller->membersEncrypted();

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

    public function testMembersEncryptedReturns422OnValidationException(): void {
        $this->request->method('getParam')
            ->willReturnCallback(fn($name) => match ($name) {
                'password' => 'short',
                default => null,
            });
        $this->csvService->method('exportMemberList')
            ->willReturn(['content' => 'csv', 'filename' => 'members.csv']);
        $this->encryptedService->method('createEncryptedExport')
            ->willThrowException(new ValidationException('Password too weak'));

        $response = $this->controller->membersEncrypted();

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

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

        $response = $this->controller->membersEncrypted();

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

    // ── POST /api/v1/export/fees/encrypted ─────────────────────────

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

        $response = $this->controller->feesEncrypted();

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

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

        $response = $this->controller->feesEncrypted();

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

    public function testFeesEncryptedReturnsDownload(): void {
        $this->request->method('getParam')
            ->willReturnCallback(fn($name) => match ($name) {
                'password' => 'secret',
                'year' => '2026',
                default => null,
            });
        $this->csvService->method('exportFeeRecords')
            ->willReturn(['content' => 'csv', 'filename' => 'fees.csv']);
        $this->encryptedService->method('createEncryptedExport')
            ->willReturn(['content' => 'zip', 'filename' => 'export.zip']);

        $response = $this->controller->feesEncrypted();

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

    public function testFeesEncryptedReturns422OnValidationException(): void {
        $this->request->method('getParam')
            ->willReturnCallback(fn($name) => match ($name) {
                'password' => 'short',
                'year' => '2026',
                default => null,
            });
        $this->csvService->method('exportFeeRecords')
            ->willReturn(['content' => 'csv', 'filename' => 'fees.csv']);
        $this->encryptedService->method('createEncryptedExport')
            ->willThrowException(new ValidationException('weak'));

        $response = $this->controller->feesEncrypted();

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

    // ── POST /api/v1/export/birthdays/encrypted ────────────────────

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

        $response = $this->controller->birthdaysEncrypted();

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

    public function testBirthdaysEncryptedReturnsDownload(): void {
        $this->request->method('getParam')
            ->willReturnCallback(fn($name) => match ($name) {
                'password' => 'secret',
                default => null,
            });
        $this->csvService->method('exportBirthdayList')
            ->willReturn(['content' => 'csv', 'filename' => 'birthdays.csv']);
        $this->encryptedService->method('createEncryptedExport')
            ->willReturn(['content' => 'zip', 'filename' => 'export.zip']);

        $response = $this->controller->birthdaysEncrypted();

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

    // ── GET /api/v1/export/entity-types ────────────────────────────

    public function testEntityTypesReturns200(): void {
        $this->entityExportService->method('getEntityTypes')
            ->willReturn(['members', 'families']);

        $response = $this->controller->entityTypes();

        $this->assertSame(Http::STATUS_OK, $response->getStatus());
        $this->assertSame(['members', 'families'], $response->getData());
    }

    // ── GET /api/v1/export/entity/{type} ───────────────────────────

    public function testEntityReturnsDownloadResponse(): void {
        $this->entityExportService->method('exportEntity')
            ->with('members')
            ->willReturn(['content' => 'csv', 'filename' => 'members.csv']);

        $response = $this->controller->entity('members');

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

    public function testEntityReturns400OnInvalidType(): void {
        $this->entityExportService->method('exportEntity')
            ->willThrowException(new \InvalidArgumentException('Unknown type'));

        $response = $this->controller->entity('invalid');

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

    public function testEntityReturns500OnException(): void {
        $this->entityExportService->method('exportEntity')
            ->willThrowException(new \RuntimeException('fail'));

        $response = $this->controller->entity('members');

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

    // ── POST /api/v1/export/bundle/sensitive ───────────────────────

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

        $response = $this->controller->bundleSensitive();

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

    public function testBundleSensitiveReturns500OnException(): void {
        $this->request->method('getParam')
            ->willReturnCallback(fn($name) => match ($name) {
                'confirm' => 'true',
                default => null,
            });
        $this->entityExportService->method('exportFullBundle')
            ->willThrowException(new \RuntimeException('fail'));

        $response = $this->controller->bundleSensitive();

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

    // ── POST /api/v1/export/banking/encrypted ──────────────────────

    public function testBankingEncryptedReturns403WithoutPermission(): void {
        $this->encryptedService->method('assertBankingExportPermission')
            ->willThrowException(new ValidationException('No banking permission'));

        $response = $this->controller->bankingEncrypted();

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

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

        $response = $this->controller->bankingEncrypted();

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

    public function testBankingEncryptedReturnsDownload(): void {
        $this->request->method('getParam')
            ->willReturnCallback(fn($name) => match ($name) {
                'password' => 'secret',
                default => null,
            });
        $this->csvService->method('exportBankingList')
            ->willReturn(['content' => 'csv', 'filename' => 'banking.csv']);
        $this->encryptedService->method('createEncryptedExport')
            ->willReturn(['content' => 'zip', 'filename' => 'banking.zip']);

        $response = $this->controller->bankingEncrypted();

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

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

        $response = $this->controller->bankingEncrypted();

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