<?php

namespace MintyPHP\Service\Audit;

use MintyPHP\Domain\Taxonomy\SystemAuditChannel;
use MintyPHP\Domain\Taxonomy\SystemAuditOutcome;
use MintyPHP\Http\ApiAuth;
use MintyPHP\Http\RequestContext;
use MintyPHP\Http\SessionStoreInterface;
use MintyPHP\Repository\Audit\SystemAuditLogRepositoryInterface;
use MintyPHP\Repository\Support\RepoQuery;
use MintyPHP\Service\Settings\SettingsSystemAuditGateway;

class SystemAuditService
{
    private const RETENTION_DAYS_MIN = 30;
    private const RETENTION_DAYS_MAX = 1095;
    private const RETENTION_DAYS_FALLBACK = 365;

    public function __construct(
        private readonly SystemAuditLogRepositoryInterface $systemAuditLogRepository,
        private readonly SystemAuditRedactionService $systemAuditRedactionService,
        private readonly SettingsSystemAuditGateway $settingsSystemAuditGateway,
        private readonly SessionStoreInterface $sessionStore
    ) {
    }

    public function record(
        string $eventType,
        string $outcome = SystemAuditOutcome::Success->value,
        array $context = []
    ): ?int {
        if (!$this->isEnabled()) {
            return null;
        }

        $eventType = $this->normalizeEventType($eventType);
        if ($eventType === '') {
            return null;
        }

        $outcome = $this->normalizeOutcome($outcome);
        $requestContext = RequestContext::context();
        $session = $this->sessionStore->all();

        // Callers can override actor_user_id/actor_tenant_id in $context; otherwise auto-detect from session/API auth.
        $fallbackActorUserId = !empty($session['user']['id'])
            ? (int) $session['user']['id']
            : (ApiAuth::isAuthenticated() ? ApiAuth::userId() : 0);
        $fallbackActorTenantId = !empty($session['current_tenant']['id'])
            ? (int) $session['current_tenant']['id']
            : (ApiAuth::isAuthenticated() ? (int) (ApiAuth::tenantId() ?? 0) : 0);

        $actorUserId = (int) ($context['actor_user_id'] ?? $fallbackActorUserId);
        $actorTenantId = (int) ($context['actor_tenant_id'] ?? $fallbackActorTenantId);
        $targetId = (int) ($context['target_id'] ?? 0);

        $targetType = trim((string) ($context['target_type'] ?? ''));
        $targetUuid = trim((string) ($context['target_uuid'] ?? ''));
        $errorCode = trim((string) ($context['error_code'] ?? ''));
        $metadata = is_array($context['metadata'] ?? null) ? $context['metadata'] : [];

        $before = is_array($context['before'] ?? null) ? $context['before'] : [];
        $after = is_array($context['after'] ?? null) ? $context['after'] : [];
        if ($before !== [] || $after !== []) {
            $metadata = [
                ...$metadata,
                ...$this->systemAuditRedactionService->buildChangeSet($before, $after),
            ];
        }

        $metadata = $this->systemAuditRedactionService->redactMetadata($metadata);
        $metadataJson = $metadata !== [] ? json_encode($metadata, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null;
        if (!is_string($metadataJson)) {
            $metadataJson = null;
        }

        $ip = trim((string) ($requestContext['ip'] ?? ''));
        $userAgent = trim((string) ($requestContext['user_agent'] ?? ''));

        $payload = [
            'event_uuid' => RepoQuery::uuidV4(),
            'request_id' => $this->normalizeRequestId((string) ($requestContext['request_id'] ?? '')),
            'channel' => $this->normalizeChannel((string) ($requestContext['channel'] ?? '')),
            'event_type' => $eventType,
            'outcome' => $outcome,
            'error_code' => $errorCode !== '' ? substr($errorCode, 0, 100) : null,
            'actor_user_id' => $actorUserId > 0 ? $actorUserId : null,
            'actor_tenant_id' => $actorTenantId > 0 ? $actorTenantId : null,
            'target_type' => $targetType !== '' ? substr($targetType, 0, 64) : null,
            'target_id' => $targetId > 0 ? $targetId : null,
            'target_uuid' => $targetUuid !== '' ? substr($targetUuid, 0, 36) : null,
            'method' => $this->normalizeMethod((string) ($requestContext['method'] ?? '')),
            'path' => $this->normalizePath((string) ($requestContext['path'] ?? '')),
            // IP and user agent are stored as hashes only — privacy by design, allows correlation without plaintext storage.
            'ip_hash' => $ip !== '' ? $this->systemAuditRedactionService->hashValue($ip) : null,
            'user_agent_hash' => $userAgent !== '' ? $this->systemAuditRedactionService->hashValue($userAgent) : null,
            'metadata_json' => $metadataJson,
        ];

        try {
            $id = $this->systemAuditLogRepository->create($payload);
            return is_int($id) ? $id : null;
        } catch (\Throwable) {
            // Fail-open by design: audit logging must never break business flow.
            return null;
        }
    }

    public function listPaged(array $filters): array
    {
        return $this->systemAuditLogRepository->listPaged($filters);
    }

    public function find(int $id): ?array
    {
        return $this->systemAuditLogRepository->find($id);
    }

    public function filterOptions(int $limit = 200): array
    {
        return $this->systemAuditLogRepository->listFilterOptions($limit);
    }

    public function purgeExpired(): int
    {
        return $this->systemAuditLogRepository->purgeOlderThanDays($this->retentionDays());
    }

    public function retentionDays(): int
    {
        $days = (int) $this->settingsSystemAuditGateway->getSystemAuditRetentionDays();
        if ($days < self::RETENTION_DAYS_MIN || $days > self::RETENTION_DAYS_MAX) {
            return self::RETENTION_DAYS_FALLBACK;
        }
        return $days;
    }

    public function isEnabled(): bool
    {
        return $this->settingsSystemAuditGateway->isSystemAuditEnabled();
    }

    private function normalizeEventType(string $eventType): string
    {
        $eventType = trim($eventType);
        if ($eventType === '') {
            return '';
        }
        return substr($eventType, 0, 64);
    }

    private function normalizeOutcome(string $outcome): string
    {
        return SystemAuditOutcome::normalizeOr($outcome, SystemAuditOutcome::Success)->value;
    }

    private function normalizeChannel(string $channel): string
    {
        return SystemAuditChannel::normalizeOr($channel, SystemAuditChannel::Web)->value;
    }

    private function normalizeMethod(string $method): ?string
    {
        $method = strtoupper(trim($method));
        if ($method === '') {
            return null;
        }
        return substr($method, 0, 8);
    }

    private function normalizePath(string $path): ?string
    {
        $path = trim($path);
        if ($path === '') {
            return null;
        }
        return substr($path, 0, 255);
    }

    private function normalizeRequestId(string $requestId): ?string
    {
        $requestId = strtolower(trim($requestId));
        if ($requestId === '') {
            return null;
        }
        if (preg_match('/^[a-f0-9-]{36}$/', $requestId) !== 1) {
            return null;
        }
        return $requestId;
    }
}
