<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Service;

use DateTime;
use OCA\Mitgliederverwaltung\Db\AuditLog;
use OCA\Mitgliederverwaltung\Db\AuditLogMapper;
use OCP\DB\Exception;
use OCP\IUserSession;

/**
 * Service for logging all create/update/delete actions with field-level diffs.
 *
 * For encrypted fields (IBAN, Kontoinhaber, Allergien), the value is stored
 * as '[verschluesselt]' to avoid leaking sensitive data into the audit log.
 *
 * Part of Issue #39.
 */
class AuditService {

    /** Fields that contain encrypted data — values are masked in the log. */
    private const ENCRYPTED_FIELDS = [
        'ibanEncrypted',
        'iban_encrypted',
        'kontoinhaberEncrypted',
        'kontoinhaber_encrypted',
        'allergienEncrypted',
        'allergien_encrypted',
    ];

    private AuditLogMapper $mapper;
    private IUserSession $userSession;

    public function __construct(
        AuditLogMapper $mapper,
        IUserSession $userSession
    ) {
        $this->mapper = $mapper;
        $this->userSession = $userSession;
    }

    /**
     * Log the creation of an entity.
     * Records all non-null fields as new values.
     *
     * @param array $entityData Serialized entity data
     * @param string $entityType e.g. 'member', 'family'
     * @param int $entityId
     * @throws Exception
     */
    public function logCreate(array $entityData, string $entityType, int $entityId): void {
        $now = (new DateTime())->format('Y-m-d H:i:s');
        $userId = $this->getCurrentUserId();

        foreach ($entityData as $field => $value) {
            if ($value === null || $field === 'id') {
                continue;
            }

            $displayValue = $this->maskIfEncrypted($field, (string)$value);

            $entry = new AuditLog();
            $entry->setZeitpunkt($now);
            $entry->setNcUserId($userId);
            $entry->setAktion('create');
            $entry->setEntitaet($entityType);
            $entry->setEntitaetId($entityId);
            $entry->setFeld($field);
            $entry->setAlterWert(null);
            $entry->setNeuerWert($displayValue);
            $this->mapper->insert($entry);
        }
    }

    /**
     * Log an update by computing field-level diffs between old and new state.
     *
     * @param array $oldData Serialized old entity data
     * @param array $newData Serialized new entity data
     * @param string $entityType
     * @param int $entityId
     * @throws Exception
     */
    public function logUpdate(array $oldData, array $newData, string $entityType, int $entityId): void {
        $now = (new DateTime())->format('Y-m-d H:i:s');
        $userId = $this->getCurrentUserId();

        foreach ($newData as $field => $newValue) {
            if ($field === 'id') {
                continue;
            }

            $oldValue = $oldData[$field] ?? null;

            // Normalize for comparison
            $oldStr = $oldValue !== null ? (string)$oldValue : null;
            $newStr = $newValue !== null ? (string)$newValue : null;

            if ($oldStr === $newStr) {
                continue;
            }

            $displayOld = $oldStr !== null ? $this->maskIfEncrypted($field, $oldStr) : null;
            $displayNew = $newStr !== null ? $this->maskIfEncrypted($field, $newStr) : null;

            $entry = new AuditLog();
            $entry->setZeitpunkt($now);
            $entry->setNcUserId($userId);
            $entry->setAktion('update');
            $entry->setEntitaet($entityType);
            $entry->setEntitaetId($entityId);
            $entry->setFeld($field);
            $entry->setAlterWert($displayOld);
            $entry->setNeuerWert($displayNew);
            $this->mapper->insert($entry);
        }
    }

    /**
     * Log the deletion of an entity.
     *
     * @throws Exception
     */
    public function logDelete(string $entityType, int $entityId): void {
        $entry = new AuditLog();
        $entry->setZeitpunkt((new DateTime())->format('Y-m-d H:i:s'));
        $entry->setNcUserId($this->getCurrentUserId());
        $entry->setAktion('delete');
        $entry->setEntitaet($entityType);
        $entry->setEntitaetId($entityId);
        $this->mapper->insert($entry);
    }

    /**
     * Log a soft-delete of an entity.
     *
     * @throws Exception
     */
    public function logSoftDelete(string $entityType, int $entityId): void {
        $entry = new AuditLog();
        $entry->setZeitpunkt((new DateTime())->format('Y-m-d H:i:s'));
        $entry->setNcUserId($this->getCurrentUserId());
        $entry->setAktion('soft-delete');
        $entry->setEntitaet($entityType);
        $entry->setEntitaetId($entityId);
        $this->mapper->insert($entry);
    }

    /**
     * Query the audit log with optional filters.
     *
     * @return array{data: array, total: int}
     * @throws Exception
     */
    public function getLog(
        ?string $entitaet = null,
        ?int $entitaetId = null,
        ?string $ncUserId = null,
        ?string $fromDate = null,
        ?string $toDate = null,
        ?int $limit = null,
        ?int $offset = null
    ): array {
        $entries = $this->mapper->findFiltered(
            $entitaet, $entitaetId, $ncUserId, $fromDate, $toDate, $limit, $offset
        );
        $total = $this->mapper->countFiltered($entitaet, $entitaetId, $ncUserId);

        return [
            'data' => array_map(fn(AuditLog $e) => $e->jsonSerialize(), $entries),
            'total' => $total,
        ];
    }

    /**
     * If the field is an encrypted field, return '[verschluesselt]' instead
     * of the actual value to prevent leaking sensitive data.
     */
    private function maskIfEncrypted(string $field, string $value): string {
        if (in_array($field, self::ENCRYPTED_FIELDS, true)) {
            return '[verschluesselt]';
        }
        return $value;
    }

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