<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Service;

use DateTime;
use OCA\Mitgliederverwaltung\Db\Family;
use OCA\Mitgliederverwaltung\Db\FamilyMapper;
use OCA\Mitgliederverwaltung\Db\MemberMapper;
use OCA\Mitgliederverwaltung\Service\AuditService;
use OCA\Mitgliederverwaltung\Validation\IbanValidator;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\Exception;
use Psr\Log\LoggerInterface;

/**
 * Service layer for family CRUD operations.
 *
 * Part of Issue #28.
 */
class FamilyService {

    private FamilyMapper $familyMapper;
    private MemberMapper $memberMapper;
    private AuditService $auditService;
    private LoggerInterface $logger;

    public function __construct(
        FamilyMapper $familyMapper,
        MemberMapper $memberMapper,
        AuditService $auditService,
        LoggerInterface $logger
    ) {
        $this->familyMapper = $familyMapper;
        $this->memberMapper = $memberMapper;
        $this->auditService = $auditService;
        $this->logger = $logger;
    }

    /**
     * Create a new family.
     *
     * @throws ValidationException
     * @throws Exception
     */
    public function create(array $data): array {
        $this->validateRequiredFields($data);
        $this->validateIban($data);

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

        $family = new Family();
        $family->setName(trim($data['name']));
        $family->setKontoinhaberEncrypted($data['kontoinhaberEncrypted'] ?? null);
        $family->setIbanEncrypted($data['ibanEncrypted'] ?? null);
        $family->setBic($data['bic'] ?? null);
        $family->setKreditinstitut($data['kreditinstitut'] ?? null);
        $family->setCreatedAt($now);
        $family->setUpdatedAt($now);

        /** @var Family $family */
        $family = $this->familyMapper->insert($family);

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

        return $this->buildFamilyResponse($family);
    }

    /**
     * Find a family by ID with linked members.
     *
     * @throws DoesNotExistException
     * @throws MultipleObjectsReturnedException
     * @throws Exception
     */
    public function find(int $id): array {
        $family = $this->familyMapper->findById($id);
        return $this->buildFamilyResponse($family);
    }

    /**
     * Find all families.
     *
     * @return array[]
     * @throws Exception
     */
    public function findAll(?int $limit = null, ?int $offset = null): array {
        $families = $this->familyMapper->findAll($limit, $offset);
        return array_map(fn(Family $f) => $this->buildFamilyResponse($f), $families);
    }

    /**
     * Search families by name.
     *
     * @return array[]
     * @throws Exception
     */
    public function search(string $name): array {
        $families = $this->familyMapper->findByName($name);
        return array_map(fn(Family $f) => $this->buildFamilyResponse($f), $families);
    }

    /**
     * Count all families.
     *
     * @throws Exception
     */
    public function countAll(): int {
        return $this->familyMapper->countAll();
    }

    /**
     * Update a family.
     *
     * @throws DoesNotExistException
     * @throws MultipleObjectsReturnedException
     * @throws ValidationException
     * @throws Exception
     */
    public function update(int $id, array $data): array {
        $this->validateIban($data);
        $family = $this->familyMapper->findById($id);
        $oldData = $family->jsonSerialize();

        if (isset($data['name'])) {
            if (trim($data['name']) === '') {
                throw new ValidationException('Familienname darf nicht leer sein.');
            }
            $family->setName(trim($data['name']));
        }
        if (array_key_exists('kontoinhaberEncrypted', $data)) {
            $family->setKontoinhaberEncrypted($data['kontoinhaberEncrypted']);
        }
        if (array_key_exists('ibanEncrypted', $data)) {
            $family->setIbanEncrypted($data['ibanEncrypted']);
        }
        if (array_key_exists('bic', $data)) {
            $family->setBic($data['bic']);
        }
        if (array_key_exists('kreditinstitut', $data)) {
            $family->setKreditinstitut($data['kreditinstitut']);
        }

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

        /** @var Family $family */
        $family = $this->familyMapper->update($family);

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

        return $this->buildFamilyResponse($family);
    }

    /**
     * Delete a family. Unlinks all members first.
     *
     * @throws DoesNotExistException
     * @throws MultipleObjectsReturnedException
     * @throws Exception
     */
    public function delete(int $id): void {
        $family = $this->familyMapper->findById($id);

        // Unlink all members from this family
        $members = $this->memberMapper->findByFamily($id);
        foreach ($members as $member) {
            $member->setFamilyId(null);
            $this->memberMapper->update($member);
        }

        $this->familyMapper->delete($family);
        $this->auditService->logDelete('family', $id);

        $this->logger->info('Family deleted', [
            'familyId' => $id,
            'app' => 'mitgliederverwaltung',
        ]);
    }

    /**
     * Link a member to a family.
     *
     * @throws DoesNotExistException
     * @throws MultipleObjectsReturnedException
     * @throws Exception
     */
    public function linkMember(int $familyId, int $memberId): void {
        // Verify family exists
        $this->familyMapper->findById($familyId);

        $member = $this->memberMapper->findById($memberId);
        $member->setFamilyId($familyId);
        $member->setUpdatedAt((new DateTime())->format('Y-m-d H:i:s'));
        $this->memberMapper->update($member);
    }

    /**
     * Unlink a member from a family.
     *
     * @throws DoesNotExistException
     * @throws MultipleObjectsReturnedException
     * @throws Exception
     */
    public function unlinkMember(int $familyId, int $memberId): void {
        $member = $this->memberMapper->findById($memberId);

        if ($member->getFamilyId() !== $familyId) {
            throw new ValidationException('Mitglied gehoert nicht zu dieser Familie.');
        }

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

    /**
     * Get the count of active children in a family.
     *
     * @throws Exception
     */
    public function getActiveChildrenCount(int $familyId): int {
        $members = $this->memberMapper->findByFamily($familyId);
        return count(array_filter($members, function ($member) {
            return $member->getStatus() === 'aktiv' && $member->getRolle() === 'mitglied';
        }));
    }

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

    /**
     * @throws ValidationException
     */
    private function validateRequiredFields(array $data): void {
        if (!isset($data['name']) || trim($data['name']) === '') {
            throw new ValidationException('Familienname ist erforderlich.');
        }
    }

    /**
     * Validate IBAN if provided and non-empty.
     *
     * @throws ValidationException
     */
    private function validateIban(array $data): void {
        $iban = $data['ibanEncrypted'] ?? null;
        if ($iban !== null && trim($iban) !== '') {
            $error = IbanValidator::getError($iban);
            if ($error !== null) {
                throw new ValidationException($error);
            }
        }
    }

    /**
     * Build response array with linked members.
     *
     * @throws Exception
     */
    private function buildFamilyResponse(Family $family): array {
        $result = $family->jsonSerialize();
        $members = $this->memberMapper->findByFamily($family->getId());

        $result['members'] = array_map(fn($m) => [
            'id' => $m->getId(),
            'vorname' => $m->getVorname(),
            'nachname' => $m->getNachname(),
            'rolle' => $m->getRolle(),
            'status' => $m->getStatus(),
        ], $members);

        $result['activeChildrenCount'] = count(array_filter($members, function ($m) {
            return $m->getStatus() === 'aktiv' && $m->getRolle() === 'mitglied';
        }));

        return $result;
    }
}
