<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Integration;

use OCA\Mitgliederverwaltung\Controller\InjuryController;
use OCA\Mitgliederverwaltung\Service\InjuryService;
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 InjuryController API endpoints.
 *
 * Part of Issue #65.
 */
class InjuryControllerTest extends TestCase {

    private InjuryController $controller;
    private InjuryService&MockObject $injuryService;
    private IRequest&MockObject $request;
    private LoggerInterface&MockObject $logger;

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

        $this->injuryService = $this->createMock(InjuryService::class);
        $this->request = $this->createMock(IRequest::class);
        $this->logger = $this->createMock(LoggerInterface::class);

        $this->controller = new InjuryController(
            'mitgliederverwaltung',
            $this->request,
            $this->injuryService,
            $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/injuries ────────────────────────────────────────

    public function testIndexReturns200WithInjuryList(): void {
        $injuries = [['id' => 1, 'datum' => '2026-03-15']];

        $this->request->method('getParam')->willReturn(null);
        $this->injuryService->method('findAll')->willReturn($injuries);

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

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

    public function testIndexWithAllFilters(): void {
        $this->request->method('getParam')
            ->willReturnCallback(function ($name) {
                return match ($name) {
                    'memberId' => '1',
                    'lagerId' => '2',
                    'dateFrom' => '2026-01-01',
                    'dateTo' => '2026-12-31',
                    default => null,
                };
            });
        $this->injuryService->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->injuryService->method('findAll')
            ->willThrowException(new \RuntimeException('DB error'));

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

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

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

    public function testShowReturns200ForExistingInjury(): void {
        $injury = ['id' => 1, 'datum' => '2026-03-15', 'beschreibung' => 'Sturz'];
        $this->injuryService->method('find')->with(1)->willReturn($injury);

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

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

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

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

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

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

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

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

    // ── POST /api/v1/injuries ───────────────────────────────────────

    public function testCreateReturns201OnSuccess(): void {
        $data = ['beschreibung' => 'Sturz', 'datum' => '2026-03-15', 'memberId' => 1];
        $this->setRequestBody($data);
        $this->injuryService->method('create')->willReturn(['id' => 1, ...$data]);

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

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

    public function testCreateWithInvolvedMemberIds(): void {
        $data = [
            'beschreibung' => 'Sturz',
            'datum' => '2026-03-15',
            'memberId' => 1,
            'involvedMemberIds' => [2, 3],
        ];
        $this->setRequestBody($data);
        $this->injuryService->method('create')->willReturn(['id' => 1]);

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

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

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

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

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

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

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

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

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

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

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

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

    public function testUpdateWithInvolvedMemberIds(): void {
        $this->setRequestBody(['beschreibung' => 'Updated', 'involvedMemberIds' => [2, 3]]);
        $this->injuryService->method('update')
            ->willReturn(['id' => 1, 'beschreibung' => 'Updated']);

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

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

    public function testUpdateReturns404ForMissingInjury(): void {
        $this->setRequestBody(['beschreibung' => 'test']);
        $this->injuryService->method('update')
            ->willThrowException(new DoesNotExistException(''));

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

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

    public function testUpdateReturns400OnValidationError(): void {
        $this->setRequestBody(['datum' => 'invalid']);
        $this->injuryService->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(['beschreibung' => 'test']);
        $this->injuryService->method('update')
            ->willThrowException(new \RuntimeException('DB error'));

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

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

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

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

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

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

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

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

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

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

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

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

    // ── Involved Members ────────────────────────────────────────────

    public function testAddInvolvedReturns400WhenMemberIdMissing(): void {
        $response = $this->controller->addInvolved(1);

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

    public function testAddInvolvedReturns201OnSuccess(): void {
        $this->setRequestBody(['memberId' => 2]);
        $this->injuryService->expects($this->once())
            ->method('addInvolvedMember')
            ->with(1, 2);

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

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

    public function testAddInvolvedReturns500OnException(): void {
        $this->setRequestBody(['memberId' => 2]);
        $this->injuryService->method('addInvolvedMember')
            ->willThrowException(new \RuntimeException('DB error'));

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

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

    // ── Remove involved member ──────────────────────────────────────

    public function testRemoveInvolvedReturns200OnSuccess(): void {
        $this->injuryService->expects($this->once())
            ->method('removeInvolvedMember')
            ->with(1, 2);

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

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

    public function testRemoveInvolvedReturns500OnException(): void {
        $this->injuryService->method('removeInvolvedMember')
            ->willThrowException(new \RuntimeException('DB error'));

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

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