<?php

namespace MintyPHP\Module\Helpdesk\Service;

use MintyPHP\Module\Helpdesk\Repository\HandoverRepository;
use MintyPHP\Module\Helpdesk\Repository\HandoverRevisionRepository;

/** @api Called from pages/helpdesk/handovers action files */
class HandoverRevisionService
{
    public const CHANGE_TYPE_INITIAL = 'initial';
    public const CHANGE_TYPE_FIELDS = 'fields';
    public const CHANGE_TYPE_STATUS = 'status';
    public const CHANGE_TYPE_BOTH = 'both';
    public const CHANGE_TYPE_RESTORE = 'restore';

    public function __construct(
        private readonly HandoverRevisionRepository $revisionRepository,
        private readonly HandoverRepository $handoverRepository,
    ) {
    }

    /**
     * Create a new revision snapshot.
     *
     * @param array<string, mixed> $fieldValues
     */
    public function createRevision(
        int $tenantId,
        int $handoverId,
        array $fieldValues,
        string $status,
        int $schemaVersion,
        int $userId,
        string $changeType = self::CHANGE_TYPE_FIELDS,
    ): ?int {
        $nextRevision = $this->revisionRepository->getLatestRevisionNumber($tenantId, $handoverId) + 1;

        $fieldValuesJson = json_encode($fieldValues, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        return $this->revisionRepository->insert([
            'tenant_id' => $tenantId,
            'handover_id' => $handoverId,
            'revision' => $nextRevision,
            'field_values' => $fieldValuesJson,
            'status' => $status,
            'schema_version' => $schemaVersion,
            'change_type' => $changeType,
            'changed_by' => $userId,
        ]);
    }

    /**
     * List all revisions for a handover, newest first.
     *
     * @return list<array<string, mixed>>
     */
    public function listByHandover(int $tenantId, int $handoverId): array
    {
        return $this->revisionRepository->listByHandover($tenantId, $handoverId);
    }

    /**
     * Find a specific revision.
     *
     * @return array<string, mixed>|null
     */
    public function findRevision(int $tenantId, int $handoverId, int $revision): ?array
    {
        return $this->revisionRepository->findByRevision($tenantId, $handoverId, $revision);
    }

    /**
     * Compute the diff between two sets of field values.
     *
     * @param array<string, mixed> $oldValues
     * @param array<string, mixed> $newValues
     * @return array<string, array{type: string, old: mixed, new: mixed}>
     */
    public function computeDiff(array $oldValues, array $newValues): array
    {
        $diff = [];
        $allKeys = array_unique(array_merge(array_keys($oldValues), array_keys($newValues)));

        foreach ($allKeys as $key) {
            $oldVal = $oldValues[$key] ?? null;
            $newVal = $newValues[$key] ?? null;

            if ($oldVal === null && $newVal !== null) {
                $diff[$key] = ['type' => 'added', 'old' => null, 'new' => $newVal];
            } elseif ($oldVal !== null && $newVal === null) {
                $diff[$key] = ['type' => 'removed', 'old' => $oldVal, 'new' => null];
            } elseif ((string) $oldVal !== (string) $newVal) {
                $diff[$key] = ['type' => 'changed', 'old' => $oldVal, 'new' => $newVal];
            }
        }

        return $diff;
    }

    /**
     * Restore a handover to a previous revision's values.
     * Creates a new revision with the restored values.
     *
     * @return array{ok: bool, errors: array<string, string>}
     */
    public function restoreRevision(
        int $tenantId,
        int $handoverId,
        int $revisionNumber,
        int $userId,
        string $permissionLevel,
    ): array {
        if ($permissionLevel !== HandoverService::PERMISSION_MANAGE) {
            return ['ok' => false, 'errors' => ['general' => t('Only managers can restore revisions')]];
        }

        $revision = $this->revisionRepository->findByRevision($tenantId, $handoverId, $revisionNumber);
        if ($revision === null) {
            return ['ok' => false, 'errors' => ['general' => t('Revision not found')]];
        }

        $handover = $this->handoverRepository->findById($tenantId, $handoverId);
        if ($handover === null) {
            return ['ok' => false, 'errors' => ['general' => t('Handover not found')]];
        }

        $restoredValues = json_decode((string) ($revision['field_values'] ?? '{}'), true);
        if (!is_array($restoredValues)) {
            $restoredValues = [];
        }

        $restoredStatus = (string) ($revision['status'] ?? $handover['status']);

        $this->handoverRepository->beginTransaction();
        try {
            // Update the handover with restored values.
            $fieldValuesJson = json_encode($restoredValues, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            $updatedFields = $this->handoverRepository->updateFieldValues($tenantId, $handoverId, $fieldValuesJson, $userId);
            if (!$updatedFields) {
                throw new \RuntimeException('Failed to restore handover field values');
            }

            $currentStatus = (string) ($handover['status'] ?? '');
            if ($restoredStatus !== $currentStatus) {
                $updatedStatus = $this->handoverRepository->updateStatus($tenantId, $handoverId, $restoredStatus, $userId);
                if (!$updatedStatus) {
                    throw new \RuntimeException('Failed to restore handover status');
                }
            }

            // Determine schema version from current handover.
            $schema = json_decode((string) ($handover['schema_snapshot'] ?? '{}'), true);
            $schemaVersion = is_array($schema) ? (int) ($schema['version'] ?? 1) : 1;

            // Create a new revision marking the restore.
            $revisionId = $this->createRevision(
                $tenantId,
                $handoverId,
                $restoredValues,
                $restoredStatus,
                $schemaVersion,
                $userId,
                self::CHANGE_TYPE_RESTORE
            );
            if ($revisionId === null) {
                throw new \RuntimeException('Failed to create restore revision');
            }

            $this->handoverRepository->commitTransaction();
        } catch (\Throwable) {
            try {
                $this->handoverRepository->rollbackTransaction();
            } catch (\Throwable) {
                // no-op
            }

            return ['ok' => false, 'errors' => ['general' => t('Failed to restore')]];
        }

        return ['ok' => true, 'errors' => []];
    }

    /**
     * Get a human-readable label for a change type.
     */
    public static function changeTypeLabel(string $changeType): string
    {
        return match ($changeType) {
            self::CHANGE_TYPE_INITIAL => t('Created'),
            self::CHANGE_TYPE_FIELDS => t('Fields changed'),
            self::CHANGE_TYPE_STATUS => t('Status changed'),
            self::CHANGE_TYPE_BOTH => t('Fields and status changed'),
            self::CHANGE_TYPE_RESTORE => t('Restored'),
            default => $changeType,
        };
    }
}
