<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Integration;

use OCA\Mitgliederverwaltung\Controller\FamilyController;
use OCA\Mitgliederverwaltung\Service\FamilyService;
use OCA\Mitgliederverwaltung\Service\ValidationException;
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 FamilyController API endpoints.
 */
class FamilyControllerTest extends TestCase {

    private FamilyController $controller;
    private FamilyService&MockObject $familyService;
    private IRequest&MockObject $request;
    private LoggerInterface&MockObject $logger;

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

        $this->familyService = $this->createMock(FamilyService::class);
        $this->request = $this->createMock(IRequest::class);
        $this->logger = $this->createMock(LoggerInterface::class);

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

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

    public function testIndexReturns200WithFamilyList(): void {
        $this->request->method('getParam')->willReturn(null);
        $this->familyService->method('findAll')->willReturn([
            ['id' => 1, 'name' => 'Mustermann'],
        ]);
        $this->familyService->method('countAll')->willReturn(1);

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

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

    public function testIndexSearchReturnsFilteredResults(): void {
        $this->request->method('getParam')
            ->willReturnCallback(fn($name, $default = null) => match ($name) {
                'search' => 'Mueller',
                default => $default,
            });
        $this->familyService->method('search')
            ->with('Mueller')
            ->willReturn([['id' => 1, 'name' => 'Mueller']]);
        $this->familyService->method('countAll')->willReturn(1);

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

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

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

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

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

    // ── GET /api/v1/families/{id} (show) ───────────────────────────

    public function testShowReturns200ForExistingFamily(): void {
        $family = ['id' => 1, 'name' => 'Mustermann'];
        $this->familyService->method('find')->with(1)->willReturn($family);

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

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

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

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

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

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

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

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

    // ── POST /api/v1/families (create) ─────────────────────────────

    public function testCreateReturns201OnSuccess(): void {
        $this->familyService->method('create')
            ->willReturn(['id' => 1, 'name' => 'Mustermann']);

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

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

    public function testCreateReturns400OnValidationError(): void {
        $this->familyService->method('create')
            ->willThrowException(new ValidationException('Name required'));

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

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

    public function testCreateReturns500OnException(): void {
        $this->familyService->method('create')
            ->willThrowException(new \RuntimeException('fail'));

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

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

    // ── PUT /api/v1/families/{id} (update) ─────────────────────────

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

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

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

    public function testUpdateReturns404ForMissingFamily(): void {
        $this->familyService->method('update')
            ->willThrowException(new DoesNotExistException(''));

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

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

    public function testUpdateReturns400OnValidationError(): void {
        $this->familyService->method('update')
            ->willThrowException(new ValidationException('Invalid'));

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

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

    public function testUpdateReturns500OnException(): void {
        $this->familyService->method('update')
            ->willThrowException(new \RuntimeException('fail'));

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

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

    // ── DELETE /api/v1/families/{id} (destroy) ─────────────────────

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

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

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

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

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

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

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

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

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

    // ── POST /api/v1/families/{id}/members/{memberId} (linkMember) ──

    public function testLinkMemberReturns201OnSuccess(): void {
        $this->familyService->expects($this->once())
            ->method('linkMember')->with(1, 5);

        $response = $this->controller->linkMember(1, 5);

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

    public function testLinkMemberReturns404ForMissingEntity(): void {
        $this->familyService->method('linkMember')
            ->willThrowException(new DoesNotExistException(''));

        $response = $this->controller->linkMember(1, 9999);

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

    public function testLinkMemberReturns500OnException(): void {
        $this->familyService->method('linkMember')
            ->willThrowException(new \RuntimeException('fail'));

        $response = $this->controller->linkMember(1, 5);

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

    // ── DELETE /api/v1/families/{id}/members/{memberId} (unlinkMember)

    public function testUnlinkMemberReturns200OnSuccess(): void {
        $this->familyService->expects($this->once())
            ->method('unlinkMember')->with(1, 5);

        $response = $this->controller->unlinkMember(1, 5);

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

    public function testUnlinkMemberReturns404ForMissingMember(): void {
        $this->familyService->method('unlinkMember')
            ->willThrowException(new DoesNotExistException(''));

        $response = $this->controller->unlinkMember(1, 9999);

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

    public function testUnlinkMemberReturns400OnValidationError(): void {
        $this->familyService->method('unlinkMember')
            ->willThrowException(new ValidationException('Cannot unlink'));

        $response = $this->controller->unlinkMember(1, 5);

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

    public function testUnlinkMemberReturns500OnException(): void {
        $this->familyService->method('unlinkMember')
            ->willThrowException(new \RuntimeException('fail'));

        $response = $this->controller->unlinkMember(1, 5);

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