<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Integration;

use OCA\Mitgliederverwaltung\Controller\StufeController;
use OCA\Mitgliederverwaltung\Db\Stufe;
use OCA\Mitgliederverwaltung\Service\StufeService;
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 StufeController API endpoints.
 */
class StufeControllerTest extends TestCase {

    private StufeController $controller;
    private StufeService&MockObject $stufeService;
    private IRequest&MockObject $request;
    private LoggerInterface&MockObject $logger;

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

        $this->stufeService = $this->createMock(StufeService::class);
        $this->request = $this->createMock(IRequest::class);
        $this->logger = $this->createMock(LoggerInterface::class);

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

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

    public function testIndexReturns200(): void {
        $stufe = $this->createStubStufe(1, 'Woelflinge');
        $this->stufeService->method('findAll')->willReturn([$stufe]);

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

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

    public function testIndexReturns500OnException(): void {
        $this->stufeService->method('findAll')
            ->willThrowException(new \RuntimeException('fail'));

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

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

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

    public function testCreateReturns201OnSuccess(): void {
        $stufe = new Stufe();
        $stufe->setName('Woelflinge');
        $this->stufeService->method('create')->willReturn($stufe);

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

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

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

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

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

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

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

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

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

    public function testUpdateReturns200OnSuccess(): void {
        $stufe = new Stufe();
        $stufe->setName('Updated');
        $this->stufeService->method('update')->willReturn($stufe);

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

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

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

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

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

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

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

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

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

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

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

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

    public function testDestroyReturns200OnSuccess(): void {
        $this->stufeService->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 testDestroyReturns404ForMissingStufe(): void {
        $this->stufeService->method('delete')
            ->willThrowException(new DoesNotExistException(''));

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

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

    public function testDestroyReturns400WhenMembersAssigned(): void {
        $this->stufeService->method('delete')
            ->willThrowException(new ValidationException('Members still assigned'));

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

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

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

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

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

    // ── GET /api/v1/stufen/suggestions ─────────────────────────────

    public function testSuggestionsReturns200(): void {
        $suggestions = [
            ['memberId' => 1, 'currentStufe' => 'Woelflinge', 'suggestedStufe' => 'Jungpfadfinder'],
        ];
        $this->stufeService->method('getSuggestedChanges')->willReturn($suggestions);

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

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

    public function testSuggestionsReturns500OnException(): void {
        $this->stufeService->method('getSuggestedChanges')
            ->willThrowException(new \RuntimeException('fail'));

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

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

    // ── GET /api/v1/stufen/members/{memberId}/history ──────────────

    public function testMemberHistoryReturns200(): void {
        $historyEntry = $this->createStubStufe(1, 'Woelflinge');
        $this->stufeService->method('getMemberStufeHistory')
            ->with(1)
            ->willReturn([$historyEntry]);

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

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

    public function testMemberHistoryReturns500OnException(): void {
        $this->stufeService->method('getMemberStufeHistory')
            ->willThrowException(new \RuntimeException('fail'));

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

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

    /**
     * Create a simple stub object that implements jsonSerialize.
     */
    private function createStubStufe(int $id, string $name): object {
        return new class($id, $name) implements \JsonSerializable {
            public function __construct(
                private int $id,
                private string $name
            ) {}

            public function jsonSerialize(): array {
                return ['id' => $this->id, 'name' => $this->name];
            }
        };
    }
}
