<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Integration;

use OCA\Mitgliederverwaltung\Controller\LagerController;
use OCA\Mitgliederverwaltung\Service\LagerService;
use OCA\Mitgliederverwaltung\Service\ValidationException;
use OCA\Mitgliederverwaltung\Tests\TestInputStream;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\IRequest;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;

/**
 * Integration-level tests for LagerController API endpoints.
 *
 * Part of Issue #65.
 */
class LagerControllerTest extends TestCase {

    private LagerController $controller;
    private LagerService&MockObject $lagerService;
    private IRequest&MockObject $request;
    private LoggerInterface&MockObject $logger;

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

        $this->lagerService = $this->createMock(LagerService::class);
        $this->request = $this->createMock(IRequest::class);
        $this->logger = $this->createMock(LoggerInterface::class);

        $this->controller = new LagerController(
            'mitgliederverwaltung',
            $this->request,
            $this->lagerService,
            $this->logger
        );
    }

    protected function tearDown(): void {
        TestInputStream::unregister();
        parent::tearDown();
    }

    private function setRequestBody(array $data): void {
        TestInputStream::register();
        TestInputStream::setData(json_encode($data));
    }

    // ── GET /api/v1/lager ───────────────────────────────────────────

    public function testIndexReturns200WithCampList(): void {
        $camps = [
            ['id' => 1, 'name' => 'Sommerlager 2026'],
        ];

        $this->request->method('getParam')->willReturn(null);
        $this->lagerService->method('findAll')->willReturn($camps);

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

        $this->assertSame(Http::STATUS_OK, $response->getStatus());
        $this->assertCount(1, $response->getData()['data']);
    }

    public function testIndexWithYearFilter(): void {
        $this->request->method('getParam')
            ->willReturnCallback(function ($name) {
                return match ($name) {
                    'year' => '2026',
                    default => null,
                };
            });
        $this->lagerService->method('findAll')->willReturn([]);

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

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

    public function testIndexWithStufeFilter(): void {
        $this->request->method('getParam')
            ->willReturnCallback(function ($name) {
                return match ($name) {
                    'stufeId' => '1',
                    default => null,
                };
            });
        $this->lagerService->method('findAll')->willReturn([]);

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

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

    public function testIndexReturns500OnException(): void {
        $this->request->method('getParam')->willReturn(null);
        $this->lagerService->method('findAll')
            ->willThrowException(new \RuntimeException('DB error'));

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

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

    // ── GET /api/v1/lager/{id} ──────────────────────────────────────

    public function testShowReturns200ForExistingCamp(): void {
        $camp = ['id' => 1, 'name' => 'Sommerlager 2026'];
        $this->lagerService->method('find')->with(1)->willReturn($camp);

        $response = $this->controller->show(1);

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

    public function testShowReturns404ForMissingCamp(): void {
        $this->lagerService->method('find')
            ->willThrowException(new DoesNotExistException(''));

        $response = $this->controller->show(9999);

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

    public function testShowReturns500OnException(): void {
        $this->lagerService->method('find')
            ->willThrowException(new \RuntimeException('DB error'));

        $response = $this->controller->show(1);

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

    // ── POST /api/v1/lager ──────────────────────────────────────────

    public function testCreateReturns201OnSuccess(): void {
        $campData = ['name' => 'Pfingstlager', 'startdatum' => '2026-06-01'];
        $this->setRequestBody($campData);
        $this->lagerService->method('create')
            ->willReturn(['id' => 1, ...$campData]);

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

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

    public function testCreateWithStufeIds(): void {
        $campData = ['name' => 'Pfingstlager', 'stufeIds' => [1, 2]];
        $this->setRequestBody($campData);
        $this->lagerService->method('create')
            ->willReturn(['id' => 1, 'name' => 'Pfingstlager']);

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

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

    public function testCreateReturns400OnValidationError(): void {
        $this->setRequestBody(['name' => '']);
        $this->lagerService->method('create')
            ->willThrowException(new ValidationException('Fehlende Pflichtfelder: name'));

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

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

    public function testCreateReturns500OnException(): void {
        $this->setRequestBody(['name' => 'Test']);
        $this->lagerService->method('create')
            ->willThrowException(new \RuntimeException('DB error'));

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

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

    // ── PUT /api/v1/lager/{id} ──────────────────────────────────────

    public function testUpdateReturns200OnSuccess(): void {
        $this->setRequestBody(['name' => 'Updated']);
        $this->lagerService->method('update')
            ->willReturn(['id' => 1, 'name' => 'Updated']);

        $response = $this->controller->update(1);

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

    public function testUpdateWithStufeIds(): void {
        $this->setRequestBody(['name' => 'Updated', 'stufeIds' => [1, 2]]);
        $this->lagerService->method('update')
            ->willReturn(['id' => 1, 'name' => 'Updated']);

        $response = $this->controller->update(1);

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

    public function testUpdateReturns404ForMissingCamp(): void {
        $this->setRequestBody(['name' => 'test']);
        $this->lagerService->method('update')
            ->willThrowException(new DoesNotExistException(''));

        $response = $this->controller->update(9999);

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

    public function testUpdateReturns400OnValidationError(): void {
        $this->setRequestBody(['startdatum' => 'invalid']);
        $this->lagerService->method('update')
            ->willThrowException(new ValidationException('Invalid date'));

        $response = $this->controller->update(1);

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

    public function testUpdateReturns500OnException(): void {
        $this->setRequestBody(['name' => 'test']);
        $this->lagerService->method('update')
            ->willThrowException(new \RuntimeException('DB error'));

        $response = $this->controller->update(1);

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

    // ── DELETE /api/v1/lager/{id} ───────────────────────────────────

    public function testDestroyReturns200OnSuccess(): void {
        $this->lagerService->expects($this->once())->method('delete')->with(1);

        $response = $this->controller->destroy(1);

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

    public function testDestroyReturns404ForMissingCamp(): void {
        $this->lagerService->method('delete')
            ->willThrowException(new DoesNotExistException(''));

        $response = $this->controller->destroy(9999);

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

    public function testDestroyReturns500OnException(): void {
        $this->lagerService->method('delete')
            ->willThrowException(new \RuntimeException('DB error'));

        $response = $this->controller->destroy(1);

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

    // ── Participant management ──────────────────────────────────────

    public function testAddTeilnehmerReturns400WhenMemberIdMissing(): void {
        // No body / empty body
        $response = $this->controller->addTeilnehmer(1);

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

    public function testAddTeilnehmerReturns201OnSuccess(): void {
        $this->setRequestBody(['memberId' => 1, 'rolle' => 'Leiter']);
        $this->lagerService->expects($this->once())
            ->method('addTeilnehmer')
            ->with(1, 1, 'Leiter');

        $response = $this->controller->addTeilnehmer(1);

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

    public function testAddTeilnehmerWithDefaultRole(): void {
        $this->setRequestBody(['memberId' => 1]);
        $this->lagerService->expects($this->once())
            ->method('addTeilnehmer')
            ->with(1, 1, 'Teilnehmer');

        $response = $this->controller->addTeilnehmer(1);

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

    public function testAddTeilnehmerReturns500OnException(): void {
        $this->setRequestBody(['memberId' => 1]);
        $this->lagerService->method('addTeilnehmer')
            ->willThrowException(new \RuntimeException('DB error'));

        $response = $this->controller->addTeilnehmer(1);

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

    public function testRemoveTeilnehmerReturns200OnSuccess(): void {
        $this->lagerService->expects($this->once())
            ->method('removeTeilnehmer')
            ->with(1, 2);

        $response = $this->controller->removeTeilnehmer(1, 2);

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

    public function testRemoveTeilnehmerReturns500OnException(): void {
        $this->lagerService->method('removeTeilnehmer')
            ->willThrowException(new \RuntimeException('DB error'));

        $response = $this->controller->removeTeilnehmer(1, 2);

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

    // ── File management ─────────────────────────────────────────────

    public function testAddFileReturns400WhenFilePathMissing(): void {
        // No body / empty body
        $response = $this->controller->addFile(1);

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

    public function testAddFileReturns400WhenFilePathEmpty(): void {
        $this->setRequestBody(['filePath' => '']);

        $response = $this->controller->addFile(1);

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

    public function testAddFileReturns201OnSuccess(): void {
        $this->setRequestBody(['filePath' => '/path/to/file.pdf', 'label' => 'Teilnehmerliste']);
        $this->lagerService->method('addFile')->willReturn(42);

        $response = $this->controller->addFile(1);

        $this->assertSame(Http::STATUS_CREATED, $response->getStatus());
        $this->assertSame(42, $response->getData()['id']);
    }

    public function testAddFileReturns500OnException(): void {
        $this->setRequestBody(['filePath' => '/path/to/file.pdf']);
        $this->lagerService->method('addFile')
            ->willThrowException(new \RuntimeException('DB error'));

        $response = $this->controller->addFile(1);

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

    public function testRemoveFileReturns200OnSuccess(): void {
        $this->lagerService->expects($this->once())
            ->method('removeFile')
            ->with(42);

        $response = $this->controller->removeFile(1, 42);

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

    public function testRemoveFileReturns500OnException(): void {
        $this->lagerService->method('removeFile')
            ->willThrowException(new \RuntimeException('DB error'));

        $response = $this->controller->removeFile(1, 42);

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

    // ── Member camp history ─────────────────────────────────────────

    public function testMemberHistoryReturns200(): void {
        $this->lagerService->method('getMemberLagerHistory')
            ->willReturn([['id' => 1, 'name' => 'Sommerlager']]);

        $response = $this->controller->memberHistory(1);

        $this->assertSame(Http::STATUS_OK, $response->getStatus());
        $this->assertCount(1, $response->getData()['data']);
    }

    public function testMemberHistoryReturns500OnException(): void {
        $this->lagerService->method('getMemberLagerHistory')
            ->willThrowException(new \RuntimeException('DB error'));

        $response = $this->controller->memberHistory(1);

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