<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Service;

use DateTime;
use OCA\Mitgliederverwaltung\Db\Injury;
use OCA\Mitgliederverwaltung\Db\InjuryMapper;
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 OCP\IUserSession;
use Psr\Log\LoggerInterface;

/**
 * Service for injury record CRUD and involved-member management.
 *
 * Part of Issue #57.
 */
class InjuryService {

    private InjuryMapper $injuryMapper;
    private MemberMapper $memberMapper;
    private IDBConnection $db;
    private AuditService $auditService;
    private IUserSession $userSession;
    private LoggerInterface $logger;

    public function __construct(
        InjuryMapper $injuryMapper,
        MemberMapper $memberMapper,
        IDBConnection $db,
        AuditService $auditService,
        IUserSession $userSession,
        LoggerInterface $logger
    ) {
        $this->injuryMapper = $injuryMapper;
        $this->memberMapper = $memberMapper;
        $this->db = $db;
        $this->auditService = $auditService;
        $this->userSession = $userSession;
        $this->logger = $logger;
    }

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

    /**
     * Create a new injury record.
     *
     * @param array $data Injury fields
     * @param int[] $involvedMemberIds Additional involved member IDs
     * @return array Injury with involved members
     * @throws ValidationException
     * @throws Exception
     */
    public function create(array $data, array $involvedMemberIds = []): array {
        $this->validateRequired($data);

        $user = $this->userSession->getUser();
        $userId = $user ? $user->getUID() : 'system';

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

        $injury = new Injury();
        $injury->setDatum($data['datum']);
        $injury->setUhrzeit($data['uhrzeit'] ?? null);
        $injury->setMemberId((int)$data['memberId']);
        $injury->setLagerId(isset($data['lagerId']) ? (int)$data['lagerId'] : null);
        $injury->setActivityName($data['activityName'] ?? null);
        $injury->setBeschreibung(trim($data['beschreibung']));
        $injury->setCreatedBy($userId);
        $injury->setCreatedAt($now);

        /** @var Injury $injury */
        $injury = $this->injuryMapper->insert($injury);

        // Add involved members
        foreach ($involvedMemberIds as $memberId) {
            $this->addInvolvedMember($injury->getId(), (int)$memberId);
        }

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

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

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

    /**
     * List all injuries with optional filters.
     *
     * @return array[]
     * @throws Exception
     */
    public function findAll(
        ?int $memberId = null,
        ?int $lagerId = null,
        ?string $dateFrom = null,
        ?string $dateTo = null
    ): array {
        if ($memberId !== null) {
            $injuries = $this->injuryMapper->findByMemberId($memberId);
        } elseif ($lagerId !== null) {
            $injuries = $this->injuryMapper->findByLagerId($lagerId);
        } elseif ($dateFrom !== null && $dateTo !== null) {
            $injuries = $this->injuryMapper->findByDateRange($dateFrom, $dateTo);
        } else {
            $injuries = $this->injuryMapper->findAll();
        }

        return array_map(fn(Injury $i) => $this->buildResponse($i), $injuries);
    }

    /**
     * Get injuries for a specific member.
     *
     * @return array[]
     * @throws Exception
     */
    public function getInjuriesForMember(int $memberId): array {
        $injuries = $this->injuryMapper->findByMemberId($memberId);
        return array_map(fn(Injury $i) => $this->buildResponse($i), $injuries);
    }

    /**
     * Get injuries for a specific camp.
     *
     * @return array[]
     * @throws Exception
     */
    public function getInjuriesForLager(int $lagerId): array {
        $injuries = $this->injuryMapper->findByLagerId($lagerId);
        return array_map(fn(Injury $i) => $this->buildResponse($i), $injuries);
    }

    /**
     * Get injuries within a date range.
     *
     * @return array[]
     * @throws Exception
     */
    public function getInjuriesForDateRange(string $from, string $to): array {
        $injuries = $this->injuryMapper->findByDateRange($from, $to);
        return array_map(fn(Injury $i) => $this->buildResponse($i), $injuries);
    }

    /**
     * Update an existing injury.
     *
     * @param int[] $involvedMemberIds If provided, replaces all involved members
     * @throws DoesNotExistException
     * @throws MultipleObjectsReturnedException
     * @throws Exception
     */
    public function update(int $id, array $data, ?array $involvedMemberIds = null): array {
        $injury = $this->injuryMapper->findById($id);
        $oldData = $injury->jsonSerialize();

        if (isset($data['datum'])) {
            $injury->setDatum($data['datum']);
        }
        if (array_key_exists('uhrzeit', $data)) {
            $injury->setUhrzeit($data['uhrzeit']);
        }
        if (isset($data['memberId'])) {
            $injury->setMemberId((int)$data['memberId']);
        }
        if (array_key_exists('lagerId', $data)) {
            $injury->setLagerId($data['lagerId'] !== null ? (int)$data['lagerId'] : null);
        }
        if (array_key_exists('activityName', $data)) {
            $injury->setActivityName($data['activityName']);
        }
        if (isset($data['beschreibung'])) {
            $injury->setBeschreibung(trim($data['beschreibung']));
        }

        /** @var Injury $injury */
        $injury = $this->injuryMapper->update($injury);

        // Update involved members if provided
        if ($involvedMemberIds !== null) {
            $this->clearInvolvedMembers($id);
            foreach ($involvedMemberIds as $memberId) {
                $this->addInvolvedMember($id, (int)$memberId);
            }
        }

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

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

    /**
     * Delete an injury and all involved-member links.
     *
     * @throws DoesNotExistException
     * @throws MultipleObjectsReturnedException
     * @throws Exception
     */
    public function delete(int $id): void {
        $injury = $this->injuryMapper->findById($id);

        $this->clearInvolvedMembers($id);
        $this->injuryMapper->delete($injury);
        $this->auditService->logDelete('injury', $id);
    }

    // ── Involved Members ─────────────────────────────────────────────

    /**
     * Add an involved member to an injury.
     *
     * @throws Exception
     */
    public function addInvolvedMember(int $injuryId, int $memberId): void {
        $qb = $this->db->getQueryBuilder();
        $qb->insert('mv_injury_involved')
            ->values([
                'injury_id' => $qb->createNamedParameter($injuryId, IQueryBuilder::PARAM_INT),
                'member_id' => $qb->createNamedParameter($memberId, IQueryBuilder::PARAM_INT),
            ]);
        $qb->executeStatement();
    }

    /**
     * Remove an involved member from an injury.
     *
     * @throws Exception
     */
    public function removeInvolvedMember(int $injuryId, int $memberId): void {
        $qb = $this->db->getQueryBuilder();
        $qb->delete('mv_injury_involved')
            ->where($qb->expr()->eq('injury_id', $qb->createNamedParameter($injuryId, IQueryBuilder::PARAM_INT)))
            ->andWhere($qb->expr()->eq('member_id', $qb->createNamedParameter($memberId, IQueryBuilder::PARAM_INT)));
        $qb->executeStatement();
    }

    /**
     * Clear all involved members from an injury.
     */
    private function clearInvolvedMembers(int $injuryId): void {
        $qb = $this->db->getQueryBuilder();
        $qb->delete('mv_injury_involved')
            ->where($qb->expr()->eq('injury_id', $qb->createNamedParameter($injuryId, IQueryBuilder::PARAM_INT)));
        $qb->executeStatement();
    }

    /**
     * Get involved member IDs and names for an injury.
     *
     * @return array[]
     */
    private function getInvolvedMembers(int $injuryId): array {
        $qb = $this->db->getQueryBuilder();
        $qb->select('member_id')
            ->from('mv_injury_involved')
            ->where($qb->expr()->eq('injury_id', $qb->createNamedParameter($injuryId, IQueryBuilder::PARAM_INT)));

        $result = $qb->executeQuery();
        $members = [];
        while ($row = $result->fetch()) {
            $memberId = (int)$row['member_id'];
            try {
                $member = $this->memberMapper->findById($memberId);
                $members[] = [
                    'memberId' => $memberId,
                    'vorname' => $member->getVorname(),
                    'nachname' => $member->getNachname(),
                ];
            } catch (\Exception $e) {
                $members[] = [
                    'memberId' => $memberId,
                    'vorname' => 'Unbekannt',
                    'nachname' => '',
                ];
            }
        }
        $result->closeCursor();
        return $members;
    }

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

    /**
     * Build full response with involved members and member/Lager names.
     */
    private function buildResponse(Injury $injury): array {
        $data = $injury->jsonSerialize();

        // Resolve injured member name
        try {
            $member = $this->memberMapper->findById($injury->getMemberId());
            $data['memberVorname'] = $member->getVorname();
            $data['memberNachname'] = $member->getNachname();
        } catch (\Exception $e) {
            $data['memberVorname'] = 'Unbekannt';
            $data['memberNachname'] = '';
        }

        // Resolve Lager name if applicable
        if ($injury->getLagerId() !== null) {
            try {
                $qb = $this->db->getQueryBuilder();
                $qb->select('name')
                    ->from('mv_lager')
                    ->where($qb->expr()->eq('id', $qb->createNamedParameter($injury->getLagerId(), IQueryBuilder::PARAM_INT)));
                $result = $qb->executeQuery();
                $row = $result->fetch();
                $result->closeCursor();
                $data['lagerName'] = $row ? $row['name'] : null;
            } catch (\Exception $e) {
                $data['lagerName'] = null;
            }
        } else {
            $data['lagerName'] = null;
        }

        $data['involvedMembers'] = $this->getInvolvedMembers($injury->getId());

        return $data;
    }

    private function validateRequired(array $data): void {
        $required = ['datum', 'memberId', 'beschreibung'];
        $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)
            );
        }
    }
}
