<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Service;

use DateTime;
use OCA\Mitgliederverwaltung\Db\Lager;
use OCA\Mitgliederverwaltung\Db\LagerMapper;
use OCA\Mitgliederverwaltung\Db\LagerTeilnehmerMapper;
use OCA\Mitgliederverwaltung\Db\MemberMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use Psr\Log\LoggerInterface;

/**
 * Service for Lager (camp) CRUD, participant management, and Stufen linking.
 *
 * Part of Issue #55.
 */
class LagerService {

    private LagerMapper $lagerMapper;
    private LagerTeilnehmerMapper $teilnehmerMapper;
    private MemberMapper $memberMapper;
    private IDBConnection $db;
    private AuditService $auditService;
    private LoggerInterface $logger;

    public function __construct(
        LagerMapper $lagerMapper,
        LagerTeilnehmerMapper $teilnehmerMapper,
        MemberMapper $memberMapper,
        IDBConnection $db,
        AuditService $auditService,
        LoggerInterface $logger
    ) {
        $this->lagerMapper = $lagerMapper;
        $this->teilnehmerMapper = $teilnehmerMapper;
        $this->memberMapper = $memberMapper;
        $this->db = $db;
        $this->auditService = $auditService;
        $this->logger = $logger;
    }

    // ── CRUD ─────────────────────────────────────────────────────────

    /**
     * Create a new camp.
     *
     * @param array $data Camp fields
     * @param int[] $stufeIds Stufen to associate
     * @return array Camp with Stufen and participants
     * @throws ValidationException
     * @throws Exception
     */
    public function create(array $data, array $stufeIds = []): array {
        $this->validateRequired($data);

        $now = (new DateTime())->format('Y-m-d H:i:s');

        $lager = new Lager();
        $lager->setName(trim($data['name']));
        $lager->setStartdatum($data['startdatum']);
        $lager->setEnddatum($data['enddatum']);
        $lager->setOrt($data['ort'] ?? null);
        $lager->setBeschreibung($data['beschreibung'] ?? null);
        $lager->setFilesFolderPath($data['filesFolderPath'] ?? null);
        $lager->setCreatedAt($now);
        $lager->setUpdatedAt($now);

        /** @var Lager $lager */
        $lager = $this->lagerMapper->insert($lager);

        // Link Stufen
        foreach ($stufeIds as $stufeId) {
            $this->addStufe($lager->getId(), (int)$stufeId);
        }

        $this->auditService->logCreate($lager->jsonSerialize(), 'lager', $lager->getId());

        return $this->buildResponse($lager);
    }

    /**
     * Find a camp by ID with all related data.
     *
     * @throws DoesNotExistException
     * @throws MultipleObjectsReturnedException
     * @throws Exception
     */
    public function find(int $id): array {
        $lager = $this->lagerMapper->findById($id);
        return $this->buildResponse($lager);
    }

    /**
     * List all camps with optional filters.
     *
     * @return array[]
     * @throws Exception
     */
    public function findAll(?int $year = null, ?int $stufeId = null): array {
        if ($year !== null) {
            $camps = $this->lagerMapper->findByYear($year);
        } elseif ($stufeId !== null) {
            $camps = $this->lagerMapper->findByStufe($stufeId);
        } else {
            $camps = $this->lagerMapper->findAll();
        }

        return array_map(fn(Lager $l) => $this->buildResponse($l), $camps);
    }

    /**
     * Update an existing camp.
     *
     * @throws DoesNotExistException
     * @throws MultipleObjectsReturnedException
     * @throws Exception
     */
    public function update(int $id, array $data, ?array $stufeIds = null): array {
        $lager = $this->lagerMapper->findById($id);
        $oldData = $lager->jsonSerialize();

        if (isset($data['name'])) {
            $lager->setName(trim($data['name']));
        }
        if (array_key_exists('startdatum', $data)) {
            $lager->setStartdatum($data['startdatum']);
        }
        if (array_key_exists('enddatum', $data)) {
            $lager->setEnddatum($data['enddatum']);
        }
        if (array_key_exists('ort', $data)) {
            $lager->setOrt($data['ort']);
        }
        if (array_key_exists('beschreibung', $data)) {
            $lager->setBeschreibung($data['beschreibung']);
        }
        if (array_key_exists('filesFolderPath', $data)) {
            $lager->setFilesFolderPath($data['filesFolderPath']);
        }

        $lager->setUpdatedAt((new DateTime())->format('Y-m-d H:i:s'));

        /** @var Lager $lager */
        $lager = $this->lagerMapper->update($lager);

        // Update Stufen if provided
        if ($stufeIds !== null) {
            $this->clearStufen($id);
            foreach ($stufeIds as $stufeId) {
                $this->addStufe($id, (int)$stufeId);
            }
        }

        $this->auditService->logUpdate($oldData, $lager->jsonSerialize(), 'lager', $id);

        return $this->buildResponse($lager);
    }

    /**
     * Delete a camp and all associated data.
     *
     * @throws DoesNotExistException
     * @throws MultipleObjectsReturnedException
     * @throws Exception
     */
    public function delete(int $id): void {
        $lager = $this->lagerMapper->findById($id);

        // Delete participants, Stufen, files
        $this->teilnehmerMapper->deleteByLagerId($id);
        $this->clearStufen($id);
        $this->clearFiles($id);

        $this->lagerMapper->delete($lager);
        $this->auditService->logDelete('lager', $id);
    }

    // ── Participants ─────────────────────────────────────────────────

    /**
     * Add a participant to a camp.
     *
     * @throws Exception
     */
    public function addTeilnehmer(int $lagerId, int $memberId, string $rolle = 'Teilnehmer'): void {
        $this->teilnehmerMapper->addParticipant($lagerId, $memberId, $rolle);
    }

    /**
     * Remove a participant from a camp.
     *
     * @throws Exception
     */
    public function removeTeilnehmer(int $lagerId, int $memberId): void {
        $this->teilnehmerMapper->removeParticipant($lagerId, $memberId);
    }

    /**
     * Get camp history for a member.
     *
     * @return array[] Camp data with role for each participation
     * @throws Exception
     */
    public function getMemberLagerHistory(int $memberId): array {
        $participations = $this->teilnehmerMapper->findByMemberId($memberId);
        $result = [];

        foreach ($participations as $p) {
            try {
                $lager = $this->lagerMapper->findById($p->getLagerId());
                $entry = $lager->jsonSerialize();
                $entry['rolle'] = $p->getRolle();
                $result[] = $entry;
            } catch (DoesNotExistException $e) {
                // Camp may have been deleted
            }
        }

        return $result;
    }

    // ── Stufen linking ───────────────────────────────────────────────

    private function addStufe(int $lagerId, int $stufeId): void {
        $qb = $this->db->getQueryBuilder();
        $qb->insert('mv_lager_stufen')
            ->values([
                'lager_id' => $qb->createNamedParameter($lagerId, IQueryBuilder::PARAM_INT),
                'stufe_id' => $qb->createNamedParameter($stufeId, IQueryBuilder::PARAM_INT),
            ]);
        $qb->executeStatement();
    }

    private function clearStufen(int $lagerId): void {
        $qb = $this->db->getQueryBuilder();
        $qb->delete('mv_lager_stufen')
            ->where($qb->expr()->eq('lager_id', $qb->createNamedParameter($lagerId, IQueryBuilder::PARAM_INT)));
        $qb->executeStatement();
    }

    /**
     * Get Stufe IDs for a camp.
     *
     * @return int[]
     */
    private function getStufeIds(int $lagerId): array {
        $qb = $this->db->getQueryBuilder();
        $qb->select('stufe_id')
            ->from('mv_lager_stufen')
            ->where($qb->expr()->eq('lager_id', $qb->createNamedParameter($lagerId, IQueryBuilder::PARAM_INT)));

        $result = $qb->executeQuery();
        $ids = [];
        while ($row = $result->fetch()) {
            $ids[] = (int)$row['stufe_id'];
        }
        $result->closeCursor();
        return $ids;
    }

    // ── Files ────────────────────────────────────────────────────────

    /**
     * Link a file to a camp.
     *
     * @return int The file link ID
     * @throws Exception
     */
    public function addFile(int $lagerId, string $filePath, ?string $label = null): int {
        $qb = $this->db->getQueryBuilder();
        $qb->insert('mv_lager_files')
            ->values([
                'lager_id' => $qb->createNamedParameter($lagerId, IQueryBuilder::PARAM_INT),
                'file_path' => $qb->createNamedParameter($filePath),
                'label' => $qb->createNamedParameter($label),
            ]);
        $qb->executeStatement();
        return (int)$qb->getLastInsertId();
    }

    /**
     * Remove a file link from a camp.
     *
     * @throws Exception
     */
    public function removeFile(int $fileId): void {
        $qb = $this->db->getQueryBuilder();
        $qb->delete('mv_lager_files')
            ->where($qb->expr()->eq('id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
        $qb->executeStatement();
    }

    private function clearFiles(int $lagerId): void {
        $qb = $this->db->getQueryBuilder();
        $qb->delete('mv_lager_files')
            ->where($qb->expr()->eq('lager_id', $qb->createNamedParameter($lagerId, IQueryBuilder::PARAM_INT)));
        $qb->executeStatement();
    }

    /**
     * Get files linked to a camp.
     *
     * @return array[]
     */
    private function getFiles(int $lagerId): array {
        $qb = $this->db->getQueryBuilder();
        $qb->select('*')
            ->from('mv_lager_files')
            ->where($qb->expr()->eq('lager_id', $qb->createNamedParameter($lagerId, IQueryBuilder::PARAM_INT)));

        $result = $qb->executeQuery();
        $files = [];
        while ($row = $result->fetch()) {
            $files[] = [
                'id' => (int)$row['id'],
                'filePath' => $row['file_path'],
                'label' => $row['label'],
            ];
        }
        $result->closeCursor();
        return $files;
    }

    // ── Helpers ──────────────────────────────────────────────────────

    /**
     * Build full response with participants, Stufen, and files.
     */
    private function buildResponse(Lager $lager): array {
        $id = $lager->getId();
        $data = $lager->jsonSerialize();

        // Participants with member names
        $participants = $this->teilnehmerMapper->findByLagerId($id);
        $data['teilnehmer'] = array_map(function ($p) {
            $result = $p->jsonSerialize();
            try {
                $member = $this->memberMapper->findById($p->getMemberId());
                $result['vorname'] = $member->getVorname();
                $result['nachname'] = $member->getNachname();
            } catch (\Exception $e) {
                $result['vorname'] = 'Unbekannt';
                $result['nachname'] = '';
            }
            return $result;
        }, $participants);

        $data['stufeIds'] = $this->getStufeIds($id);
        $data['files'] = $this->getFiles($id);

        return $data;
    }

    private function validateRequired(array $data): void {
        $required = ['name', 'startdatum', 'enddatum'];
        $missing = [];

        foreach ($required as $field) {
            if (!isset($data[$field]) || (is_string($data[$field]) && trim($data[$field]) === '')) {
                $missing[] = $field;
            }
        }

        if (!empty($missing)) {
            throw new ValidationException(
                'Fehlende Pflichtfelder: ' . implode(', ', $missing)
            );
        }
    }
}
