<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Service;

use DateTime;
use OCA\Mitgliederverwaltung\Db\MemberMapper;
use OCA\Mitgliederverwaltung\Db\Stufe;
use OCA\Mitgliederverwaltung\Db\StufeHistory;
use OCA\Mitgliederverwaltung\Db\StufeHistoryMapper;
use OCA\Mitgliederverwaltung\Db\StufeMapper;
use OCA\Mitgliederverwaltung\Service\AuditService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\Exception;
use OCP\IUserSession;

/**
 * Service for Stufen CRUD and member assignment with history tracking.
 *
 * Part of Issue #31.
 */
class StufeService {

    private StufeMapper $stufeMapper;
    private StufeHistoryMapper $historyMapper;
    private MemberMapper $memberMapper;
    private AuditService $auditService;
    private IUserSession $userSession;

    public function __construct(
        StufeMapper $stufeMapper,
        StufeHistoryMapper $historyMapper,
        MemberMapper $memberMapper,
        AuditService $auditService,
        IUserSession $userSession
    ) {
        $this->stufeMapper = $stufeMapper;
        $this->historyMapper = $historyMapper;
        $this->memberMapper = $memberMapper;
        $this->auditService = $auditService;
        $this->userSession = $userSession;
    }

    /**
     * @return Stufe[]
     * @throws Exception
     */
    public function findAll(): array {
        return $this->stufeMapper->findAll();
    }

    /**
     * @throws DoesNotExistException
     * @throws MultipleObjectsReturnedException
     * @throws Exception
     */
    public function find(int $id): Stufe {
        return $this->stufeMapper->findById($id);
    }

    /**
     * @throws ValidationException
     * @throws Exception
     */
    public function create(array $data): Stufe {
        if (!isset($data['name']) || trim($data['name']) === '') {
            throw new ValidationException('Stufenname ist erforderlich.');
        }

        $stufe = new Stufe();
        $stufe->setName(trim($data['name']));
        $stufe->setSortOrder($data['sortOrder'] ?? 0);
        $stufe->setAgeRangeMin($data['ageRangeMin'] ?? null);
        $stufe->setAgeRangeMax($data['ageRangeMax'] ?? null);
        $stufe->setColor($data['color'] ?? null);

        /** @var Stufe $stufe */
        $stufe = $this->stufeMapper->insert($stufe);

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

        return $stufe;
    }

    /**
     * @throws DoesNotExistException
     * @throws MultipleObjectsReturnedException
     * @throws ValidationException
     * @throws Exception
     */
    public function update(int $id, array $data): Stufe {
        $stufe = $this->stufeMapper->findById($id);
        $oldData = $stufe->jsonSerialize();

        if (isset($data['name'])) {
            if (trim($data['name']) === '') {
                throw new ValidationException('Stufenname darf nicht leer sein.');
            }
            $stufe->setName(trim($data['name']));
        }
        if (array_key_exists('sortOrder', $data)) {
            $stufe->setSortOrder((int)$data['sortOrder']);
        }
        if (array_key_exists('ageRangeMin', $data)) {
            $stufe->setAgeRangeMin($data['ageRangeMin']);
        }
        if (array_key_exists('ageRangeMax', $data)) {
            $stufe->setAgeRangeMax($data['ageRangeMax']);
        }
        if (array_key_exists('color', $data)) {
            $stufe->setColor($data['color']);
        }

        /** @var Stufe $stufe */
        $stufe = $this->stufeMapper->update($stufe);

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

        return $stufe;
    }

    /**
     * Delete a Stufe. Fails if members are still assigned to it.
     *
     * @throws DoesNotExistException
     * @throws MultipleObjectsReturnedException
     * @throws ValidationException
     * @throws Exception
     */
    public function delete(int $id): void {
        $stufe = $this->stufeMapper->findById($id);

        $members = $this->memberMapper->findByStufe($id);
        if (count($members) > 0) {
            throw new ValidationException(
                'Stufe kann nicht geloescht werden, da noch ' . count($members) . ' Mitglieder zugewiesen sind.'
            );
        }

        $this->stufeMapper->delete($stufe);
        $this->auditService->logDelete('stufe', $id);
    }

    /**
     * Assign a member to a Stufe, recording history.
     *
     * @throws DoesNotExistException
     * @throws MultipleObjectsReturnedException
     * @throws Exception
     */
    public function assignMemberToStufe(int $memberId, int $stufeId): void {
        $member = $this->memberMapper->findById($memberId);
        $this->stufeMapper->findById($stufeId); // Verify Stufe exists

        $member->setStufeId($stufeId);
        $member->setUpdatedAt((new DateTime())->format('Y-m-d H:i:s'));
        $this->memberMapper->update($member);

        // Record history
        $history = new StufeHistory();
        $history->setMemberId($memberId);
        $history->setStufeId($stufeId);
        $history->setChangedAt((new DateTime())->format('Y-m-d H:i:s'));
        $history->setChangedBy($this->getCurrentUserId());
        $this->historyMapper->insert($history);
    }

    /**
     * Get a member's Stufe change history.
     *
     * @return StufeHistory[]
     * @throws Exception
     */
    public function getMemberStufeHistory(int $memberId): array {
        return $this->historyMapper->findByMemberId($memberId);
    }

    // ── Stufe change suggestions ────────────────────────────────────

    /**
     * Get advisory Stufe change suggestions for members who exceed
     * their current Stufe's age range.
     *
     * Compares each active member's current age against their Stufe's
     * age_range_max. If exceeded, suggests the next Stufe in sort order.
     *
     * Part of Issue #63.
     *
     * @return array[] Suggestions sorted by exceededBy descending
     * @throws Exception
     */
    public function getSuggestedChanges(): array {
        $members = $this->memberMapper->findAll();
        $stufen = $this->stufeMapper->findAll();

        // Build lookup maps
        $stufeById = [];
        $stufeByOrder = [];
        foreach ($stufen as $stufe) {
            $stufeById[$stufe->getId()] = $stufe;
            $stufeByOrder[$stufe->getSortOrder()] = $stufe;
        }

        $sortOrders = array_keys($stufeByOrder);
        sort($sortOrders);

        $suggestions = [];
        $now = new DateTime();

        foreach ($members as $member) {
            if ($member->getStatus() !== 'aktiv') {
                continue;
            }

            $stufeId = $member->getStufeId();
            if ($stufeId === null || !isset($stufeById[$stufeId])) {
                continue;
            }

            $currentStufe = $stufeById[$stufeId];
            $ageRangeMax = $currentStufe->getAgeRangeMax();

            if ($ageRangeMax === null) {
                continue;
            }

            if ($member->getGeburtsdatum() === null) {
                continue;
            }

            try {
                $birthday = new DateTime($member->getGeburtsdatum());
                $age = (int)$now->diff($birthday)->y;
            } catch (\Exception $e) {
                continue;
            }

            if ($age > $ageRangeMax) {
                $currentOrder = $currentStufe->getSortOrder();
                $suggestedStufe = null;

                foreach ($sortOrders as $order) {
                    if ($order > $currentOrder) {
                        $suggestedStufe = $stufeByOrder[$order];
                        break;
                    }
                }

                $suggestions[] = [
                    'memberId' => $member->getId(),
                    'vorname' => $member->getVorname(),
                    'nachname' => $member->getNachname(),
                    'alter' => $age,
                    'currentStufe' => [
                        'id' => $currentStufe->getId(),
                        'name' => $currentStufe->getName(),
                        'ageRangeMax' => $ageRangeMax,
                    ],
                    'suggestedStufe' => $suggestedStufe !== null ? [
                        'id' => $suggestedStufe->getId(),
                        'name' => $suggestedStufe->getName(),
                    ] : null,
                    'exceededBy' => $age - $ageRangeMax,
                ];
            }
        }

        usort($suggestions, fn($a, $b) => $b['exceededBy'] <=> $a['exceededBy']);

        return $suggestions;
    }

    /**
     * Count of pending suggestions (for navigation badge).
     *
     * @throws Exception
     */
    public function getSuggestionCount(): int {
        return count($this->getSuggestedChanges());
    }

    private function getCurrentUserId(): string {
        $user = $this->userSession->getUser();
        return $user ? $user->getUID() : 'system';
    }
}
