<?php

declare(strict_types=1);

namespace App\Support\Governance\Controls;

use InvalidArgumentException;

final class CanonicalControlCatalog
{
    /**
     * @var list<CanonicalControlDefinition>
     */
    private array $definitions;

    /**
     * @var list<MicrosoftSubjectBinding>
     */
    private array $microsoftBindings;

    /**
     * @param  list<array<string, mixed>>|null  $controls
     */
    public function __construct(?array $controls = null)
    {
        $controls ??= config('canonical_controls.controls', []);

        if (! is_array($controls)) {
            throw new InvalidArgumentException('Canonical controls config must define a controls array.');
        }

        $this->definitions = [];
        $this->microsoftBindings = [];

        foreach ($controls as $control) {
            if (! is_array($control)) {
                throw new InvalidArgumentException('Canonical control entries must be arrays.');
            }

            $definition = CanonicalControlDefinition::fromArray($control);

            if ($this->find($definition->controlKey) instanceof CanonicalControlDefinition) {
                throw new InvalidArgumentException(sprintf('Duplicate canonical control key [%s].', $definition->controlKey));
            }

            $this->definitions[] = $definition;

            $bindings = is_array($control['microsoft_bindings'] ?? null) ? $control['microsoft_bindings'] : [];

            foreach ($bindings as $binding) {
                if (! is_array($binding)) {
                    throw new InvalidArgumentException(sprintf('Microsoft bindings for [%s] must be arrays.', $definition->controlKey));
                }

                $this->microsoftBindings[] = MicrosoftSubjectBinding::fromArray($definition->controlKey, $binding);
            }
        }

        usort(
            $this->definitions,
            static fn (CanonicalControlDefinition $left, CanonicalControlDefinition $right): int => $left->controlKey <=> $right->controlKey,
        );
    }

    /**
     * @return list<CanonicalControlDefinition>
     */
    public function all(): array
    {
        return $this->definitions;
    }

    /**
     * @return list<CanonicalControlDefinition>
     */
    public function active(): array
    {
        return array_values(array_filter(
            $this->definitions,
            static fn (CanonicalControlDefinition $definition): bool => ! $definition->isRetired(),
        ));
    }

    public function find(string $controlKey): ?CanonicalControlDefinition
    {
        $controlKey = trim($controlKey);

        foreach ($this->definitions as $definition) {
            if ($definition->controlKey === $controlKey) {
                return $definition;
            }
        }

        return null;
    }

    /**
     * @return list<MicrosoftSubjectBinding>
     */
    public function microsoftBindings(): array
    {
        return $this->microsoftBindings;
    }

    /**
     * @return list<MicrosoftSubjectBinding>
     */
    public function microsoftBindingsForControl(string $controlKey): array
    {
        return array_values(array_filter(
            $this->microsoftBindings,
            static fn (MicrosoftSubjectBinding $binding): bool => $binding->controlKey === trim($controlKey),
        ));
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function listPayload(): array
    {
        return array_map(
            static fn (CanonicalControlDefinition $definition): array => $definition->toArray(),
            $this->all(),
        );
    }
}
