<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Integration;

use OCA\Mitgliederverwaltung\Controller\ImportController;
use OCA\Mitgliederverwaltung\Service\BundleImportService;
use OCA\Mitgliederverwaltung\Service\EntityImportService;
use OCA\Mitgliederverwaltung\Service\ImportService;
use OCA\Mitgliederverwaltung\Service\ValidationException;
use OCA\Mitgliederverwaltung\Tests\TestInputStream;
use OCP\AppFramework\Http;
use OCP\IRequest;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;

/**
 * Integration-level tests for ImportController API endpoints.
 */
class ImportControllerTest extends TestCase {

    private ImportController $controller;
    private ImportService&MockObject $importService;
    private EntityImportService&MockObject $entityImportService;
    private BundleImportService&MockObject $bundleImportService;
    private IRequest&MockObject $request;
    private LoggerInterface&MockObject $logger;

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

        $this->importService = $this->createMock(ImportService::class);
        $this->entityImportService = $this->createMock(EntityImportService::class);
        $this->bundleImportService = $this->createMock(BundleImportService::class);
        $this->request = $this->createMock(IRequest::class);
        $this->logger = $this->createMock(LoggerInterface::class);

        $this->controller = new ImportController(
            'mitgliederverwaltung',
            $this->request,
            $this->importService,
            $this->entityImportService,
            $this->bundleImportService,
            $this->logger
        );
    }

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

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

    // ── POST /api/v1/import/upload ─────────────────────────────────

    public function testUploadReturns400WhenContentMissing(): void {
        $response = $this->controller->upload();

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

    public function testUploadReturns400WhenBase64Invalid(): void {
        $this->setRequestBody(['content' => '!!!invalid-base64!!!']);

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

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

    public function testUploadReturns200OnSuccess(): void {
        $csvContent = base64_encode("Vorname;Nachname\nMax;Mustermann");
        $this->setRequestBody(['content' => $csvContent]);
        $this->importService->method('parseFile')
            ->willReturn(['columns' => ['Vorname', 'Nachname'], 'rows' => [['Max', 'Mustermann']]]);
        $this->importService->method('getTargetFields')
            ->willReturn([['key' => 'vorname', 'label' => 'Vorname']]);

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

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

    public function testUploadReturns400OnValidationException(): void {
        $csvContent = base64_encode("bad content");
        $this->setRequestBody(['content' => $csvContent]);
        $this->importService->method('parseFile')
            ->willThrowException(new ValidationException('Invalid CSV'));

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

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

    public function testUploadReturns500OnException(): void {
        $csvContent = base64_encode("content");
        $this->setRequestBody(['content' => $csvContent]);
        $this->importService->method('parseFile')
            ->willThrowException(new \RuntimeException('Parse error'));

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

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

    // ── POST /api/v1/import/preview ────────────────────────────────

    public function testPreviewReturns400WhenContentOrMappingMissing(): void {
        $response = $this->controller->preview();

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

    public function testPreviewReturns400WhenBase64Invalid(): void {
        $this->setRequestBody(['content' => '!!!invalid!!!', 'mapping' => ['0' => 'vorname']]);

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

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

    public function testPreviewReturns200OnSuccess(): void {
        $csvContent = base64_encode("Max;Mustermann");
        $this->setRequestBody([
            'content' => $csvContent,
            'mapping' => ['0' => 'vorname', '1' => 'nachname'],
        ]);
        $this->importService->method('preview')
            ->willReturn(['members' => [], 'families' => []]);

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

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

    public function testPreviewReturns400OnValidationException(): void {
        $csvContent = base64_encode("content");
        $this->setRequestBody(['content' => $csvContent, 'mapping' => ['0' => 'vorname']]);
        $this->importService->method('preview')
            ->willThrowException(new ValidationException('Mapping error'));

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

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

    public function testPreviewReturns500OnException(): void {
        $csvContent = base64_encode("content");
        $this->setRequestBody(['content' => $csvContent, 'mapping' => ['0' => 'vorname']]);
        $this->importService->method('preview')
            ->willThrowException(new \RuntimeException('error'));

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

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

    // ── POST /api/v1/import/execute ────────────────────────────────

    public function testExecuteReturns400WhenContentOrMappingMissing(): void {
        $response = $this->controller->execute();

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

    public function testExecuteReturns400WhenBase64Invalid(): void {
        $this->setRequestBody(['content' => '!!!invalid!!!', 'mapping' => ['0' => 'vorname']]);

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

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

    public function testExecuteReturns200OnSuccess(): void {
        $csvContent = base64_encode("Max;Mustermann");
        $this->setRequestBody([
            'content' => $csvContent,
            'mapping' => ['0' => 'vorname', '1' => 'nachname'],
        ]);
        $this->importService->method('execute')
            ->willReturn(['imported' => 1, 'skipped' => 0]);

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

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

    public function testExecuteReturns400OnValidationException(): void {
        $csvContent = base64_encode("content");
        $this->setRequestBody(['content' => $csvContent, 'mapping' => ['0' => 'vorname']]);
        $this->importService->method('execute')
            ->willThrowException(new ValidationException('error'));

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

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

    public function testExecuteReturns500OnException(): void {
        $csvContent = base64_encode("content");
        $this->setRequestBody(['content' => $csvContent, 'mapping' => ['0' => 'vorname']]);
        $this->importService->method('execute')
            ->willThrowException(new \RuntimeException('error'));

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

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

    // ── POST /api/v1/import/entity/upload ──────────────────────────

    public function testEntityUploadReturns400WhenContentMissing(): void {
        $response = $this->controller->entityUpload();

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

    public function testEntityUploadReturns400WhenBase64Invalid(): void {
        $this->setRequestBody(['content' => '!!!invalid!!!']);

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

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

    public function testEntityUploadReturns200OnSuccess(): void {
        $csvContent = base64_encode("Vorname;Nachname");
        $this->setRequestBody(['content' => $csvContent]);
        $this->entityImportService->method('parseFile')
            ->willReturn(['columns' => ['Vorname', 'Nachname'], 'rows' => [], 'totalRows' => 0]);
        $this->entityImportService->method('detectEntityType')
            ->willReturn(['type' => 'members', 'confidence' => 0.9]);
        $this->entityImportService->method('getImportSchema')
            ->willReturn(['targetFields' => [['key' => 'vorname']]]);
        $this->entityImportService->method('autoMapHeaders')
            ->willReturn(['0' => 'vorname', '1' => 'nachname']);

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

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

    public function testEntityUploadWithExplicitType(): void {
        $csvContent = base64_encode("Vorname;Nachname");
        $this->setRequestBody(['content' => $csvContent, 'entityType' => 'members']);
        $this->entityImportService->method('parseFile')
            ->willReturn(['columns' => ['Vorname', 'Nachname'], 'rows' => [], 'totalRows' => 0]);
        $this->entityImportService->method('detectEntityType')
            ->willReturn(['type' => null, 'confidence' => 0.0]);
        $this->entityImportService->method('getImportSchema')
            ->willReturn(['targetFields' => []]);
        $this->entityImportService->method('autoMapHeaders')
            ->willReturn([]);

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

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

    public function testEntityUploadReturns400OnValidationException(): void {
        $csvContent = base64_encode("content");
        $this->setRequestBody(['content' => $csvContent]);
        $this->entityImportService->method('parseFile')
            ->willThrowException(new ValidationException('error'));

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

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

    public function testEntityUploadReturns500OnException(): void {
        $csvContent = base64_encode("content");
        $this->setRequestBody(['content' => $csvContent]);
        $this->entityImportService->method('parseFile')
            ->willThrowException(new \RuntimeException('error'));

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

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

    // ── GET /api/v1/import/entity/schema/{type} ────────────────────

    public function testEntitySchemaReturns200ForValidType(): void {
        $schema = ['targetFields' => [['key' => 'vorname', 'label' => 'Vorname']]];
        $this->entityImportService->method('getImportSchema')
            ->with('members')
            ->willReturn($schema);

        $response = $this->controller->entitySchema('members');

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

    public function testEntitySchemaReturns400ForInvalidType(): void {
        $this->entityImportService->method('getImportSchema')
            ->willThrowException(new \InvalidArgumentException('Unknown type'));

        $response = $this->controller->entitySchema('invalid');

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

    // ── POST /api/v1/import/entity/preview ─────────────────────────

    public function testEntityPreviewReturns400WhenRequiredFieldsMissing(): void {
        $response = $this->controller->entityPreview();

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

    public function testEntityPreviewReturns400WhenBase64Invalid(): void {
        $this->setRequestBody([
            'content' => '!!!invalid!!!',
            'entityType' => 'members',
            'mapping' => ['0' => 'vorname'],
        ]);

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

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

    public function testEntityPreviewReturns200OnSuccess(): void {
        $csvContent = base64_encode("Max;Mustermann");
        $this->setRequestBody([
            'content' => $csvContent,
            'entityType' => 'members',
            'mapping' => ['0' => 'vorname', '1' => 'nachname'],
        ]);
        $this->entityImportService->method('preview')
            ->willReturn(['records' => [], 'errors' => []]);

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

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

    public function testEntityPreviewReturns500OnException(): void {
        $csvContent = base64_encode("content");
        $this->setRequestBody([
            'content' => $csvContent,
            'entityType' => 'members',
            'mapping' => ['0' => 'vorname'],
        ]);
        $this->entityImportService->method('preview')
            ->willThrowException(new \RuntimeException('error'));

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

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

    // ── POST /api/v1/import/entity/execute ─────────────────────────

    public function testEntityExecuteReturns400WhenRequiredFieldsMissing(): void {
        $response = $this->controller->entityExecute();

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

    public function testEntityExecuteReturns400WhenBase64Invalid(): void {
        $this->setRequestBody([
            'content' => '!!!invalid!!!',
            'entityType' => 'members',
            'mapping' => ['0' => 'vorname'],
        ]);

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

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

    public function testEntityExecuteReturns200OnSuccess(): void {
        $csvContent = base64_encode("Max;Mustermann");
        $this->setRequestBody([
            'content' => $csvContent,
            'entityType' => 'members',
            'mapping' => ['0' => 'vorname', '1' => 'nachname'],
        ]);
        $this->entityImportService->method('execute')
            ->willReturn(['imported' => 1]);

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

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

    public function testEntityExecuteReturns500OnException(): void {
        $csvContent = base64_encode("content");
        $this->setRequestBody([
            'content' => $csvContent,
            'entityType' => 'members',
            'mapping' => ['0' => 'vorname'],
        ]);
        $this->entityImportService->method('execute')
            ->willThrowException(new \RuntimeException('error'));

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

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

    // ── POST /api/v1/import/entity/merge ───────────────────────────

    public function testEntityMergeReturns400WhenRequiredFieldsMissing(): void {
        $response = $this->controller->entityMerge();

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

    public function testEntityMergeReturns200OnSuccess(): void {
        $this->setRequestBody([
            'entityType' => 'members',
            'resolutions' => [['existingId' => 1, 'mergedFields' => ['vorname' => 'Max']]],
        ]);
        $this->entityImportService->method('mergeConflicts')
            ->willReturn(['merged' => 1]);

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

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

    public function testEntityMergeReturns500OnException(): void {
        $this->setRequestBody([
            'entityType' => 'members',
            'resolutions' => [],
        ]);
        $this->entityImportService->method('mergeConflicts')
            ->willThrowException(new \RuntimeException('error'));

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

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

    // ── POST /api/v1/import/bundle/upload ──────────────────────────

    public function testBundleUploadReturns400WhenContentMissing(): void {
        $response = $this->controller->bundleUpload();

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

    public function testBundleUploadReturns400WhenBase64Invalid(): void {
        $this->setRequestBody(['content' => '!!!invalid!!!']);

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

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

    public function testBundleUploadReturns200OnSuccess(): void {
        $zipContent = base64_encode("fake-zip-content");
        $this->setRequestBody(['content' => $zipContent]);
        $this->bundleImportService->method('analyzeBundle')
            ->willReturn(['files' => [], 'order' => []]);

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

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

    public function testBundleUploadReturns400OnValidationException(): void {
        $zipContent = base64_encode("bad");
        $this->setRequestBody(['content' => $zipContent]);
        $this->bundleImportService->method('analyzeBundle')
            ->willThrowException(new ValidationException('Not a valid ZIP'));

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

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

    public function testBundleUploadReturns500OnException(): void {
        $zipContent = base64_encode("bad");
        $this->setRequestBody(['content' => $zipContent]);
        $this->bundleImportService->method('analyzeBundle')
            ->willThrowException(new \RuntimeException('error'));

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

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

    // ── POST /api/v1/import/bundle/preview ─────────────────────────

    public function testBundlePreviewReturns400WhenContentMissing(): void {
        $response = $this->controller->bundlePreview();

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

    public function testBundlePreviewReturns400WhenBase64Invalid(): void {
        $this->setRequestBody(['content' => '!!!invalid!!!']);

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

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

    public function testBundlePreviewReturns200OnSuccess(): void {
        $zipContent = base64_encode("fake-zip");
        $this->setRequestBody(['content' => $zipContent]);
        $this->bundleImportService->method('previewBundle')
            ->willReturn(['entities' => []]);

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

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

    public function testBundlePreviewReturns500OnException(): void {
        $zipContent = base64_encode("bad");
        $this->setRequestBody(['content' => $zipContent]);
        $this->bundleImportService->method('previewBundle')
            ->willThrowException(new \RuntimeException('error'));

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

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

    // ── POST /api/v1/import/bundle/execute ─────────────────────────

    public function testBundleExecuteReturns400WhenContentMissing(): void {
        $response = $this->controller->bundleExecute();

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

    public function testBundleExecuteReturns400WhenBase64Invalid(): void {
        $this->setRequestBody(['content' => '!!!invalid!!!']);

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

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

    public function testBundleExecuteReturns200OnSuccess(): void {
        $zipContent = base64_encode("fake-zip");
        $this->setRequestBody(['content' => $zipContent]);
        $this->bundleImportService->method('executeBundle')
            ->willReturn(['imported' => 10]);

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

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

    public function testBundleExecuteReturns500OnException(): void {
        $zipContent = base64_encode("bad");
        $this->setRequestBody(['content' => $zipContent]);
        $this->bundleImportService->method('executeBundle')
            ->willThrowException(new \RuntimeException('error'));

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

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