<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Unit;

use OCA\Mitgliederverwaltung\Service\BundleImportService;
use OCA\Mitgliederverwaltung\Service\EntityExportService;
use OCA\Mitgliederverwaltung\Service\EntityImportService;
use OCA\Mitgliederverwaltung\Service\ValidationException;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;

class BundleImportServiceTest extends TestCase {

    private BundleImportService $service;
    private EntityImportService&MockObject $entityImportService;
    private EntityExportService&MockObject $exportService;
    private LoggerInterface&MockObject $logger;

    protected function setUp(): void {
        if (!class_exists(\ZipArchive::class)) {
            $this->markTestSkipped('ZipArchive not available');
        }

        $this->entityImportService = $this->createMock(EntityImportService::class);
        $this->exportService = $this->createMock(EntityExportService::class);
        $this->logger = $this->createMock(LoggerInterface::class);

        $this->service = new BundleImportService(
            $this->entityImportService,
            $this->exportService,
            $this->logger
        );
    }

    /**
     * Create a ZIP binary string with the given CSV files.
     */
    private function createZipContent(array $files): string {
        $tmpFile = tempnam(sys_get_temp_dir(), 'test_zip_');
        $zip = new \ZipArchive();
        $zip->open($tmpFile, \ZipArchive::OVERWRITE);

        foreach ($files as $filename => $content) {
            $zip->addFromString($filename, $content);
        }

        $zip->close();
        if (!file_exists($tmpFile)) {
            return '';
        }
        $content = file_get_contents($tmpFile);
        unlink($tmpFile);
        if ($content === false) {
            return '';
        }
        return $content;
    }

    // ── analyzeBundle() ────────────────────────────────────────────

    public function testAnalyzeBundleDetectsEntityTypesByFilename(): void {
        $zipContent = $this->createZipContent([
            'Stufen.csv' => "ID;Name;Sortierung\n1;Woelflinge;1",
            'Familien.csv' => "ID;Name\n1;Mueller",
        ]);

        $this->entityImportService->method('parseFile')
            ->willReturn(['columns' => [], 'rows' => [], 'totalRows' => 1]);

        $result = $this->service->analyzeBundle($zipContent);

        $this->assertArrayHasKey('entities', $result);
        $this->assertArrayHasKey('importOrder', $result);
        $this->assertArrayHasKey('unknown', $result);

        $types = array_column($result['entities'], 'type');
        $this->assertContains('stufen', $types);
        $this->assertContains('familien', $types);
    }

    public function testAnalyzeBundleReturnsImportOrder(): void {
        $zipContent = $this->createZipContent([
            'Mitglieder.csv' => "Vorname;Nachname\nMax;Mueller",
            'Stufen.csv' => "Name\nWoelflinge",
            'Familien.csv' => "Name\nMueller",
        ]);

        $this->entityImportService->method('parseFile')
            ->willReturn(['columns' => [], 'rows' => [], 'totalRows' => 1]);

        $result = $this->service->analyzeBundle($zipContent);

        $importOrder = $result['importOrder'];
        // Stufen and Familien have no deps, so should come before Mitglieder
        $stufeIdx = array_search('stufen', $importOrder);
        $familienIdx = array_search('familien', $importOrder);
        $mitgliederIdx = array_search('mitglieder', $importOrder);

        $this->assertLessThan($mitgliederIdx, $stufeIdx);
        $this->assertLessThan($mitgliederIdx, $familienIdx);
    }

    public function testAnalyzeBundleTracksUnknownFiles(): void {
        $zipContent = $this->createZipContent([
            'Stufen.csv' => "Name\nWoelflinge",
            'RandomFile.csv' => "Foo;Bar\n1;2",
        ]);

        $this->entityImportService->method('parseFile')
            ->willReturnCallback(function () {
                return ['columns' => ['Foo', 'Bar'], 'rows' => [['1', '2']], 'totalRows' => 1];
            });
        $this->entityImportService->method('detectEntityType')
            ->willReturn(['type' => null, 'confidence' => 0.0, 'alternatives' => []]);

        $result = $this->service->analyzeBundle($zipContent);

        $this->assertContains('RandomFile.csv', $result['unknown']);
    }

    // ── analyzeBundle() error cases ────────────────────────────────

    public function testAnalyzeBundleHandlesEmptyZip(): void {
        $zipContent = $this->createZipContent([]);

        $this->expectException(ValidationException::class);
        $this->service->analyzeBundle($zipContent);
    }

    // ── Dependency graph / topological sort ────────────────────────

    public function testDependencyOrderAdressen(): void {
        $zipContent = $this->createZipContent([
            'Mitglieder.csv' => "Vorname\nMax",
            'Adressen.csv' => "Strasse\nTest",
        ]);

        $this->entityImportService->method('parseFile')
            ->willReturn(['columns' => [], 'rows' => [], 'totalRows' => 1]);

        $result = $this->service->analyzeBundle($zipContent);

        $order = $result['importOrder'];
        $mitgIdx = array_search('mitglieder', $order);
        $adrIdx = array_search('adressen', $order);

        if ($mitgIdx !== false && $adrIdx !== false) {
            $this->assertLessThan($adrIdx, $mitgIdx);
        }
    }

    public function testDependencyOrderLagerteilnehmer(): void {
        $zipContent = $this->createZipContent([
            'Lager.csv' => "Name\nSommerlager",
            'Mitglieder.csv' => "Vorname\nMax",
            'Lagerteilnehmer.csv' => "Rolle\nTeilnehmer",
        ]);

        $this->entityImportService->method('parseFile')
            ->willReturn(['columns' => [], 'rows' => [], 'totalRows' => 1]);

        $result = $this->service->analyzeBundle($zipContent);

        $order = $result['importOrder'];
        $lagerIdx = array_search('lager', $order);
        $mitgIdx = array_search('mitglieder', $order);
        $ltIdx = array_search('lagerteilnehmer', $order);

        if ($lagerIdx !== false && $ltIdx !== false) {
            $this->assertLessThan($ltIdx, $lagerIdx);
        }
        if ($mitgIdx !== false && $ltIdx !== false) {
            $this->assertLessThan($ltIdx, $mitgIdx);
        }
    }

    // ── Constructor ────────────────────────────────────────────────

    public function testConstructorBuildsFilenameMap(): void {
        // The constructor processes ENTITY_TYPES, verify it works by analyzing a known filename
        $zipContent = $this->createZipContent([
            'E-Mails.csv' => "E-Mail-Adresse\ntest@test.com",
        ]);

        $this->entityImportService->method('parseFile')
            ->willReturn(['columns' => [], 'rows' => [], 'totalRows' => 1]);

        $result = $this->service->analyzeBundle($zipContent);

        $types = array_column($result['entities'], 'type');
        $this->assertContains('emails', $types);
    }

    // ── previewBundle() ────────────────────────────────────────────

    public function testPreviewBundleReturnsResults(): void {
        $zipContent = $this->createZipContent([
            'Stufen.csv' => "Name;Sortierung\nWoelflinge;1",
        ]);

        $this->entityImportService->method('parseFile')
            ->willReturn([
                'columns' => ['Name', 'Sortierung'],
                'rows' => [['Woelflinge', '1']],
                'totalRows' => 1,
            ]);
        $this->entityImportService->method('autoMapHeaders')
            ->willReturn([0 => 'name', 1 => 'sort_order']);
        $this->entityImportService->method('preview')
            ->willReturn([
                'valid' => [['name' => 'Woelflinge']],
                'errors' => [],
                'duplicates' => [],
                'fkWarnings' => [],
                'ambiguous' => [],
                'summary' => ['total' => 1, 'toCreate' => 1, 'duplicates' => 0, 'errors' => 0],
            ]);

        $result = $this->service->previewBundle($zipContent);

        $this->assertArrayHasKey('results', $result);
        $this->assertCount(1, $result['results']);
        $this->assertEquals('stufen', $result['results'][0]['type']);
    }

    public function testPreviewBundleHandlesParseError(): void {
        $zipContent = $this->createZipContent([
            'Stufen.csv' => "Name;Sortierung\nWoelflinge;1",
        ]);

        $this->entityImportService->method('parseFile')
            ->willThrowException(new ValidationException('Parse error'));

        // The first parseFile call in analyzeBundle will fail, causing the entity to be unknown
        $result = $this->service->analyzeBundle($zipContent);

        // File detection by filename still works
        $types = array_column($result['entities'], 'type');
        $this->assertContains('stufen', $types);
    }
}
