<?php

namespace NoahVet\Reef\Domain\Validation;

class OpenApiDataValidator implements OpenApiDataValidatorInterface
{
    /**
     * @var array<int|string, string|array<string, mixed>>
     */
    private array $errors = [];

    /**
     * @param array<string, mixed> $data
     * @param array<string, mixed> $config
     *
     * @return array<int|string, string|array<string, mixed>>
     */
    #[\Override]
    public function validate(array $data, array $config): array
    {
        foreach ($config as $key => $value) {
            $this->recursiveValidateRequiredKey($key, $value, $data);
        }

        return $this->errors;
    }

    /**
     * @param array<string, mixed> $data
     */
    private function recursiveValidateRequiredKey(string $key, mixed $value, array $data, string $nodeName = ''): void
    {
        if (isset($value['anyOf'])) {
            return;
        }

        $this
            ->throwNewLogicExceptionWhenTypeDoesntExist($key, $value)
            ->manageRequiredKeys($key, $value, $data, $nodeName)
            ->manageObject($key, $value, $nodeName)
        ;
    }

    private function addError(string $key, mixed $value, string $message, ?string $template = null): void
    {
        $error = [
            'key' => $key,
            'value' => $value,
            'message' => $message,
        ];

        if (null !== $template) {
            $error['template'] = $template;
        }

        $this->errors[] = $error;
    }

    private function manageObject(string|int $key, mixed $value, string $nodeName = ''): void
    {
        if ('object' === $value['type']) {
            if (!isset($value['properties'])) {
                throw new \LogicException('The key properties should be define for the type object.');
            }

            if (!\is_array($value)) {
                $this->addError($nodeName.$key, $value, 'type.object');
            } else {
                foreach ($value['properties'] as $subValueKey => $subValueValue) {
                    $this->recursiveValidateRequiredKey(
                        $subValueKey,
                        $subValueValue,
                        $value['properties'],
                        $nodeName.$key.'.',
                    );
                }
            }
        }

        if ('array' === $value['type']) {
            if (!isset($value['items']['properties'])) {
                throw new \LogicException('The key properties of items should be define for the type object.');
            }
            foreach ($value['items']['properties'] as $subValueKey => $subValueValue) {
                $this->recursiveValidateRequiredKey(
                    $subValueKey,
                    $subValueValue,
                    $value['items']['properties'],
                    $nodeName.$key.'.',
                );
            }
        }
    }

    /**
     * @param array<string, mixed> $data
     */
    private function manageRequiredKeys(string|int $key, mixed $value, array $data, string $nodeName): self
    {
        if (
            !isset($data[$key])
            && (
                (isset($value['nullable']) && !$value['nullable']) || !isset($value['nullable'])
            )
        ) {
            $this->addError($nodeName.$key, null, 'required');
        }

        return $this;
    }

    private function throwNewLogicExceptionWhenTypeDoesntExist(string|int $key, mixed $value): self
    {
        if (!isset($value['type'])) {
            throw new \LogicException(
                \sprintf(
                    'The type key is missing for the key %s.',
                    $key,
                ),
            );
        }

        return $this;
    }
}
