<?php

namespace MintyPHP\Service\Audit;

use MintyPHP\Domain\Taxonomy\ImportAuditStatus;
use MintyPHP\Repository\Audit\ImportAuditRunRepositoryInterface;
use MintyPHP\Repository\Support\RepoQuery;

class ImportAuditService
{
    private const RETENTION_DAYS = 90;

    /**
     * @var array<int, float>
     */
    private array $startedAtByRunId = [];

    public function __construct(private readonly ImportAuditRunRepositoryInterface $importAuditRunRepository)
    {
    }

    public function startRun(
        string $profileKey,
        array $mappedTargets,
        ?string $sourceFilename,
        int $userId,
        ?int $currentTenantId
    ): ?int {
        $profileKey = trim($profileKey);
        if ($profileKey === '') {
            return null;
        }

        try {
            $runId = $this->importAuditRunRepository->createRunning([
                'run_uuid' => RepoQuery::uuidV4(),
                'profile_key' => $profileKey,
                'status' => ImportAuditStatus::Running->value,
                'source_filename' => $this->normalizeSourceFilename($sourceFilename),
                'mapped_targets_csv' => $this->normalizeMappedTargetsCsv($mappedTargets),
                'user_id' => $userId > 0 ? $userId : null,
                'current_tenant_id' => ($currentTenantId ?? 0) > 0 ? (int) $currentTenantId : null,
            ]);
        } catch (\Throwable $exception) {
            return null;
        }

        if (!$runId) {
            return null;
        }

        $this->startedAtByRunId[(int) $runId] = microtime(true);
        return (int) $runId;
    }

    public function finishRun(?int $runId, array $result, ?string $forcedStatus = null): void
    {
        $runId = (int) ($runId ?? 0);
        if ($runId <= 0) {
            return;
        }

        $rowsTotal = max(0, (int) ($result['processed'] ?? 0));
        $createdCount = max(0, (int) ($result['created'] ?? 0));
        $skippedCount = max(0, (int) ($result['skipped'] ?? 0));
        $failedCount = max(0, (int) ($result['failed'] ?? 0));

        $status = $this->normalizeStatus($forcedStatus);
        if ($status === null) {
            if (array_key_exists('ok', $result) && !($result['ok'] ?? false)) {
                $status = ImportAuditStatus::Failed->value;
            } elseif ($failedCount <= 0) {
                $status = ImportAuditStatus::Success->value;
            } elseif ($createdCount > 0 || $skippedCount > 0) {
                $status = ImportAuditStatus::Partial->value;
            } else {
                $status = ImportAuditStatus::Failed->value;
            }
        }

        $durationMs = null;
        if (isset($this->startedAtByRunId[$runId])) {
            $durationMs = (int) round((microtime(true) - $this->startedAtByRunId[$runId]) * 1000);
            if ($durationMs < 0) {
                $durationMs = 0;
            }
            unset($this->startedAtByRunId[$runId]);
        }

        $errorCodesJson = $this->encodeErrorCounts($result);

        try {
            $this->importAuditRunRepository->finishById($runId, [
                'status' => $status,
                'rows_total' => $rowsTotal,
                'created_count' => $createdCount,
                'skipped_count' => $skippedCount,
                'failed_count' => $failedCount,
                'error_codes_json' => $errorCodesJson,
                'duration_ms' => $durationMs,
            ]);
        } catch (\Throwable $exception) {
            // Fail-open: import flow must not fail because audit logging fails.
        }
    }

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

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

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

    public function purgeExpired(): int
    {
        return $this->importAuditRunRepository->purgeOlderThanDays(self::RETENTION_DAYS);
    }

    /**
     * @param array<int, string> $mappedTargets
     */
    private function normalizeMappedTargetsCsv(array $mappedTargets): ?string
    {
        $normalized = [];
        foreach ($mappedTargets as $target) {
            $value = trim((string) $target);
            if ($value === '') {
                continue;
            }
            $normalized[$value] = true;
        }
        if (!$normalized) {
            return null;
        }
        $list = array_keys($normalized);
        sort($list, SORT_STRING);
        return implode(',', $list);
    }

    private function normalizeSourceFilename(?string $sourceFilename): ?string
    {
        $sourceFilename = trim((string) ($sourceFilename ?? ''));
        if ($sourceFilename === '') {
            return null;
        }

        $base = basename(str_replace("\0", '', $sourceFilename));
        if ($base === '' || $base === '.' || $base === '..') {
            return null;
        }

        return strlen($base) > 255 ? substr($base, 0, 255) : $base;
    }

    private function normalizeStatus(?string $status): ?string
    {
        return ImportAuditStatus::tryNormalize((string) ($status ?? ''))?->value;
    }

    private function encodeErrorCounts(array $result): ?string
    {
        $counts = [];

        $fromResult = $result['error_counts'] ?? null;
        if (is_array($fromResult)) {
            foreach ($fromResult as $rawCode => $rawCount) {
                $code = trim((string) $rawCode);
                $count = (int) $rawCount;
                if ($code === '' || $count <= 0) {
                    continue;
                }
                $counts[$code] = ($counts[$code] ?? 0) + $count;
            }
        }

        if (!$counts) {
            $errors = $result['errors'] ?? [];
            if (is_array($errors)) {
                foreach ($errors as $error) {
                    if (!is_array($error)) {
                        continue;
                    }
                    $code = trim((string) ($error['code'] ?? ''));
                    if ($code === '') {
                        continue;
                    }
                    $counts[$code] = ($counts[$code] ?? 0) + 1;
                }
            }
        }

        if (!$counts) {
            return null;
        }

        ksort($counts, SORT_STRING);
        $encoded = json_encode($counts, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        return is_string($encoded) ? $encoded : null;
    }
}
