<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Unit;

use OCA\Mitgliederverwaltung\Db\Lager;
use OCA\Mitgliederverwaltung\Db\LagerMapper;
use OCA\Mitgliederverwaltung\Db\LagerTeilnehmer;
use OCA\Mitgliederverwaltung\Db\LagerTeilnehmerMapper;
use OCA\Mitgliederverwaltung\Db\Member;
use OCA\Mitgliederverwaltung\Db\MemberMapper;
use OCA\Mitgliederverwaltung\Service\AuditService;
use OCA\Mitgliederverwaltung\Service\LagerService;
use OCA\Mitgliederverwaltung\Service\ValidationException;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\DB\QueryBuilder\IExpressionBuilder;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;

class LagerServiceTest extends TestCase {

    private LagerService $service;
    private LagerMapper&MockObject $lagerMapper;
    private LagerTeilnehmerMapper&MockObject $teilnehmerMapper;
    private MemberMapper&MockObject $memberMapper;
    private IDBConnection&MockObject $db;
    private AuditService&MockObject $auditService;
    private LoggerInterface&MockObject $logger;

    protected function setUp(): void {
        $this->lagerMapper = $this->createMock(LagerMapper::class);
        $this->teilnehmerMapper = $this->createMock(LagerTeilnehmerMapper::class);
        $this->memberMapper = $this->createMock(MemberMapper::class);
        $this->db = $this->createMock(IDBConnection::class);
        $this->auditService = $this->createMock(AuditService::class);
        $this->logger = $this->createMock(LoggerInterface::class);

        $this->service = new LagerService(
            $this->lagerMapper,
            $this->teilnehmerMapper,
            $this->memberMapper,
            $this->db,
            $this->auditService,
            $this->logger
        );
    }

    private function createQueryBuilderMock(): IQueryBuilder&MockObject {
        $qb = $this->createMock(IQueryBuilder::class);
        $expr = $this->createMock(IExpressionBuilder::class);
        $expr->method('eq')->willReturn('');
        $qb->method('expr')->willReturn($expr);
        $qb->method('insert')->willReturnSelf();
        $qb->method('delete')->willReturnSelf();
        $qb->method('select')->willReturnSelf();
        $qb->method('from')->willReturnSelf();
        $qb->method('where')->willReturnSelf();
        $qb->method('andWhere')->willReturnSelf();
        $qb->method('values')->willReturnSelf();
        $qb->method('set')->willReturnSelf();
        $qb->method('createNamedParameter')->willReturn('?');
        $qb->method('executeStatement')->willReturn(1);
        $qb->method('getLastInsertId')->willReturn(1);

        $result = $this->createMock(\OCP\DB\IResult::class);
        $result->method('fetch')->willReturn(false);
        $result->method('closeCursor')->willReturn(true);
        $qb->method('executeQuery')->willReturn($result);

        return $qb;
    }

    private function createLagerEntity(int $id, string $name): Lager {
        $lager = new Lager();
        $lager->setName($name);
        $lager->setStartdatum('2024-07-01');
        $lager->setEnddatum('2024-07-14');
        $lager->setOrt('Wald');
        $lager->setCreatedAt('2024-01-01 00:00:00');
        $lager->setUpdatedAt('2024-01-01 00:00:00');
        $ref = new \ReflectionProperty($lager, 'id');
        $ref->setAccessible(true);
        $ref->setValue($lager, $id);
        return $lager;
    }

    // ── create() ───────────────────────────────────────────────────

    public function testCreateSucceeds(): void {
        $qb = $this->createQueryBuilderMock();
        $this->db->method('getQueryBuilder')->willReturn($qb);

        $lager = $this->createLagerEntity(1, 'Sommerlager');

        $this->lagerMapper->method('insert')->willReturn($lager);
        $this->teilnehmerMapper->method('findByLagerId')->willReturn([]);

        $result = $this->service->create([
            'name' => 'Sommerlager',
            'startdatum' => '2024-07-01',
            'enddatum' => '2024-07-14',
            'ort' => 'Wald',
        ]);

        $this->assertEquals('Sommerlager', $result['name']);
        $this->assertArrayHasKey('teilnehmer', $result);
        $this->assertArrayHasKey('stufeIds', $result);
        $this->assertArrayHasKey('files', $result);
    }

    public function testCreateWithStufeIds(): void {
        $qb = $this->createQueryBuilderMock();
        $this->db->method('getQueryBuilder')->willReturn($qb);

        $lager = $this->createLagerEntity(1, 'Sommerlager');
        $this->lagerMapper->method('insert')->willReturn($lager);
        $this->teilnehmerMapper->method('findByLagerId')->willReturn([]);

        $this->service->create([
            'name' => 'Sommerlager',
            'startdatum' => '2024-07-01',
            'enddatum' => '2024-07-14',
        ], [1, 2]);

        // Just verifying no exception is thrown when stufeIds are provided
        $this->assertTrue(true);
    }

    public function testCreateThrowsOnMissingName(): void {
        $this->expectException(ValidationException::class);

        $this->service->create([
            'startdatum' => '2024-07-01',
            'enddatum' => '2024-07-14',
        ]);
    }

    public function testCreateThrowsOnMissingStartdatum(): void {
        $this->expectException(ValidationException::class);

        $this->service->create([
            'name' => 'Test',
            'enddatum' => '2024-07-14',
        ]);
    }

    public function testCreateThrowsOnMissingEnddatum(): void {
        $this->expectException(ValidationException::class);

        $this->service->create([
            'name' => 'Test',
            'startdatum' => '2024-07-01',
        ]);
    }

    public function testCreateThrowsOnEmptyName(): void {
        $this->expectException(ValidationException::class);

        $this->service->create([
            'name' => '  ',
            'startdatum' => '2024-07-01',
            'enddatum' => '2024-07-14',
        ]);
    }

    // ── find() ─────────────────────────────────────────────────────

    public function testFindReturnsLagerWithDetails(): void {
        $qb = $this->createQueryBuilderMock();
        $this->db->method('getQueryBuilder')->willReturn($qb);

        $lager = $this->createLagerEntity(1, 'Sommerlager');
        $this->lagerMapper->method('findById')->with(1)->willReturn($lager);
        $this->teilnehmerMapper->method('findByLagerId')->willReturn([]);

        $result = $this->service->find(1);

        $this->assertEquals('Sommerlager', $result['name']);
        $this->assertArrayHasKey('teilnehmer', $result);
    }

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

        $this->expectException(DoesNotExistException::class);
        $this->service->find(999);
    }

    // ── findAll() ──────────────────────────────────────────────────

    public function testFindAllReturnsAllCamps(): void {
        $qb = $this->createQueryBuilderMock();
        $this->db->method('getQueryBuilder')->willReturn($qb);

        $lager1 = $this->createLagerEntity(1, 'Sommerlager');
        $lager2 = $this->createLagerEntity(2, 'Winterlager');

        $this->lagerMapper->method('findAll')->willReturn([$lager1, $lager2]);
        $this->teilnehmerMapper->method('findByLagerId')->willReturn([]);

        $result = $this->service->findAll();

        $this->assertCount(2, $result);
    }

    public function testFindAllFiltersByYear(): void {
        $qb = $this->createQueryBuilderMock();
        $this->db->method('getQueryBuilder')->willReturn($qb);

        $lager = $this->createLagerEntity(1, 'Sommerlager');
        $this->lagerMapper->method('findByYear')->with(2024)->willReturn([$lager]);
        $this->teilnehmerMapper->method('findByLagerId')->willReturn([]);

        $result = $this->service->findAll(2024);

        $this->assertCount(1, $result);
    }

    public function testFindAllFiltersByStufe(): void {
        $qb = $this->createQueryBuilderMock();
        $this->db->method('getQueryBuilder')->willReturn($qb);

        $lager = $this->createLagerEntity(1, 'Sommerlager');
        $this->lagerMapper->method('findByStufe')->with(5)->willReturn([$lager]);
        $this->teilnehmerMapper->method('findByLagerId')->willReturn([]);

        $result = $this->service->findAll(null, 5);

        $this->assertCount(1, $result);
    }

    // ── update() ───────────────────────────────────────────────────

    public function testUpdateSucceeds(): void {
        $qb = $this->createQueryBuilderMock();
        $this->db->method('getQueryBuilder')->willReturn($qb);

        $lager = $this->createLagerEntity(1, 'Sommerlager');
        $this->lagerMapper->method('findById')->with(1)->willReturn($lager);

        $updated = $this->createLagerEntity(1, 'Neuer Name');
        $this->lagerMapper->method('update')->willReturn($updated);
        $this->teilnehmerMapper->method('findByLagerId')->willReturn([]);

        $result = $this->service->update(1, ['name' => 'Neuer Name']);

        $this->assertEquals('Neuer Name', $result['name']);
    }

    public function testUpdateWithStufeIds(): void {
        $qb = $this->createQueryBuilderMock();
        $this->db->method('getQueryBuilder')->willReturn($qb);

        $lager = $this->createLagerEntity(1, 'Sommerlager');
        $this->lagerMapper->method('findById')->with(1)->willReturn($lager);
        $this->lagerMapper->method('update')->willReturn($lager);
        $this->teilnehmerMapper->method('findByLagerId')->willReturn([]);

        $result = $this->service->update(1, ['name' => 'Updated'], [1, 2]);

        $this->assertArrayHasKey('stufeIds', $result);
    }

    // ── delete() ───────────────────────────────────────────────────

    public function testDeleteSucceeds(): void {
        $qb = $this->createQueryBuilderMock();
        $this->db->method('getQueryBuilder')->willReturn($qb);

        $lager = $this->createLagerEntity(1, 'Sommerlager');
        $this->lagerMapper->method('findById')->with(1)->willReturn($lager);
        $this->teilnehmerMapper->expects($this->once())->method('deleteByLagerId')->with(1);
        $this->lagerMapper->expects($this->once())->method('delete');

        $this->service->delete(1);
    }

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

        $this->expectException(DoesNotExistException::class);
        $this->service->delete(999);
    }

    // ── Participant management ─────────────────────────────────────

    public function testAddTeilnehmer(): void {
        $this->teilnehmerMapper->expects($this->once())
            ->method('addParticipant')
            ->with(1, 10, 'Teilnehmer');

        $this->service->addTeilnehmer(1, 10);
    }

    public function testAddTeilnehmerWithRole(): void {
        $this->teilnehmerMapper->expects($this->once())
            ->method('addParticipant')
            ->with(1, 10, 'Leiter');

        $this->service->addTeilnehmer(1, 10, 'Leiter');
    }

    public function testRemoveTeilnehmer(): void {
        $this->teilnehmerMapper->expects($this->once())
            ->method('removeParticipant')
            ->with(1, 10);

        $this->service->removeTeilnehmer(1, 10);
    }

    // ── getMemberLagerHistory() ────────────────────────────────────

    public function testGetMemberLagerHistory(): void {
        $teilnehmer = new LagerTeilnehmer();
        $teilnehmer->setLagerId(1);
        $teilnehmer->setMemberId(10);
        $teilnehmer->setRolle('Teilnehmer');

        $lager = $this->createLagerEntity(1, 'Sommerlager');

        $this->teilnehmerMapper->method('findByMemberId')->with(10)->willReturn([$teilnehmer]);
        $this->lagerMapper->method('findById')->with(1)->willReturn($lager);

        $result = $this->service->getMemberLagerHistory(10);

        $this->assertCount(1, $result);
        $this->assertEquals('Teilnehmer', $result[0]['rolle']);
    }

    public function testGetMemberLagerHistorySkipsDeletedCamps(): void {
        $teilnehmer = new LagerTeilnehmer();
        $teilnehmer->setLagerId(999);
        $teilnehmer->setMemberId(10);
        $teilnehmer->setRolle('Teilnehmer');

        $this->teilnehmerMapper->method('findByMemberId')->willReturn([$teilnehmer]);
        $this->lagerMapper->method('findById')
            ->willThrowException(new DoesNotExistException(''));

        $result = $this->service->getMemberLagerHistory(10);

        $this->assertCount(0, $result);
    }

    // ── File management ────────────────────────────────────────────

    public function testAddFile(): void {
        $qb = $this->createQueryBuilderMock();
        $this->db->method('getQueryBuilder')->willReturn($qb);

        $result = $this->service->addFile(1, '/path/to/file.pdf', 'Document');

        $this->assertEquals(1, $result);
    }

    public function testRemoveFile(): void {
        $qb = $this->createQueryBuilderMock();
        $this->db->method('getQueryBuilder')->willReturn($qb);

        // Should not throw
        $this->service->removeFile(1);
        $this->assertTrue(true);
    }
}
