<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Integration;

use OCA\Mitgliederverwaltung\Controller\QueryController;
use OCA\Mitgliederverwaltung\Db\SavedQuery;
use OCA\Mitgliederverwaltung\Db\SavedQueryMapper;
use OCA\Mitgliederverwaltung\Service\QueryService;
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 QueryController API endpoints.
 */
class QueryControllerTest extends TestCase {

    private QueryController $controller;
    private QueryService&MockObject $queryService;
    private SavedQueryMapper&MockObject $savedQueryMapper;
    private IRequest&MockObject $request;
    private LoggerInterface&MockObject $logger;

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

        $this->queryService = $this->createMock(QueryService::class);
        $this->savedQueryMapper = $this->createMock(SavedQueryMapper::class);
        $this->request = $this->createMock(IRequest::class);
        $this->logger = $this->createMock(LoggerInterface::class);

        $this->controller = new QueryController(
            'mitgliederverwaltung',
            $this->request,
            $this->queryService,
            $this->savedQueryMapper,
            $this->logger,
            'testuser'
        );
    }

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

    public function testIndexReturns200WithQueries(): void {
        $query = new SavedQuery();
        $query->setName('Test Query');
        $query->setQueryJson('{"field":"vorname","op":"eq","value":"Max"}');
        $query->setCreatedBy('testuser');
        $query->setCreatedAt('2026-01-01 00:00:00');

        $this->savedQueryMapper->method('findAll')->willReturn([$query]);

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

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

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

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

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

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

    public function testCreateReturns400WhenNameAndQueryJsonMissing(): void {
        // php://input is empty, so data is empty array
        $response = $this->controller->create();

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

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

    public function testUpdateReturns404ForMissingQuery(): void {
        $this->savedQueryMapper->method('findById')
            ->willThrowException(new DoesNotExistException(''));

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

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

    public function testUpdateReturns200OnSuccess(): void {
        $query = new SavedQuery();
        $query->setName('Old Name');
        $query->setQueryJson('{"field":"vorname"}');
        $query->setCreatedBy('testuser');
        $query->setCreatedAt('2026-01-01 00:00:00');

        $this->savedQueryMapper->method('findById')->with(1)->willReturn($query);
        $this->savedQueryMapper->method('update')->willReturn($query);

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

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

    public function testUpdateReturns500OnException(): void {
        $query = new SavedQuery();
        $query->setName('Test');
        $query->setQueryJson('{}');
        $query->setCreatedBy('testuser');
        $query->setCreatedAt('2026-01-01 00:00:00');

        $this->savedQueryMapper->method('findById')->willReturn($query);
        $this->savedQueryMapper->method('update')
            ->willThrowException(new \RuntimeException('fail'));

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

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

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

    public function testDestroyReturns200OnSuccess(): void {
        $query = new SavedQuery();
        $this->savedQueryMapper->method('findById')->with(1)->willReturn($query);
        $this->savedQueryMapper->expects($this->once())->method('delete')->with($query);

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

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

    public function testDestroyReturns404ForMissingQuery(): void {
        $this->savedQueryMapper->method('findById')
            ->willThrowException(new DoesNotExistException(''));

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

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

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

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

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

    // ── POST /api/v1/queries/{id}/execute (executeSaved) ───────────

    public function testExecuteSavedReturns200OnSuccess(): void {
        $query = new SavedQuery();
        $query->setQueryJson('{"field":"vorname","op":"eq","value":"Max"}');
        $query->setSortJson(null);
        $query->setName('Test');
        $query->setCreatedBy('testuser');
        $query->setCreatedAt('2026-01-01 00:00:00');

        $this->savedQueryMapper->method('findById')->with(1)->willReturn($query);

        $result = ['data' => [['id' => 1]], 'total' => 1];
        $this->queryService->method('execute')->willReturn($result);

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

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

    public function testExecuteSavedReturns404ForMissingQuery(): void {
        $this->savedQueryMapper->method('findById')
            ->willThrowException(new DoesNotExistException(''));

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

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

    public function testExecuteSavedReturns400OnValidationException(): void {
        $query = new SavedQuery();
        $query->setQueryJson('{"invalid":"query"}');
        $query->setSortJson(null);
        $query->setName('Test');
        $query->setCreatedBy('testuser');
        $query->setCreatedAt('2026-01-01 00:00:00');

        $this->savedQueryMapper->method('findById')->willReturn($query);
        $this->queryService->method('execute')
            ->willThrowException(new ValidationException('Invalid query'));

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

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

    public function testExecuteSavedReturns500OnException(): void {
        $query = new SavedQuery();
        $query->setQueryJson('{}');
        $query->setSortJson(null);
        $query->setName('Test');
        $query->setCreatedBy('testuser');
        $query->setCreatedAt('2026-01-01 00:00:00');

        $this->savedQueryMapper->method('findById')->willReturn($query);
        $this->queryService->method('execute')
            ->willThrowException(new \RuntimeException('fail'));

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

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

    // ── POST /api/v1/queries/execute (executeAdHoc) ────────────────

    public function testExecuteAdHocReturns400WhenQueryJsonMissing(): void {
        // php://input is empty
        $response = $this->controller->executeAdHoc();

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

    // ── GET /api/v1/queries/fields ─────────────────────────────────

    public function testFieldsReturns200(): void {
        $fields = [
            ['key' => 'vorname', 'label' => 'Vorname', 'type' => 'string'],
        ];
        $this->queryService->method('getAvailableFields')->willReturn($fields);

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

        $this->assertSame(Http::STATUS_OK, $response->getStatus());
        $this->assertArrayHasKey('data', $response->getData());
    }
}
