<?php

namespace App\Services\DecisionEvents;

class ConditionEvaluator
{
    /**
     * Returns true when ALL conditions pass (AND logic).
     *
     * Each condition: { field: string, operator: string, value: mixed }
     *
     * @param  array<int, array{field: string, operator: string, value: mixed}>  $conditions
     */
    public function evaluate(array $conditions, DecisionEventContext $context): bool
    {
        foreach ($conditions as $condition) {
            if (! $this->evaluateOne($condition, $context)) {
                return false;
            }
        }

        return true;
    }

    protected function evaluateOne(array $condition, DecisionEventContext $context): bool
    {
        $field = $condition['field'] ?? '';
        $operator = $condition['operator'] ?? '=';
        $expected = $condition['value'] ?? null;

        $actual = $this->resolveField($field, $context);

        return $this->compare($actual, $operator, $expected);
    }

    protected function resolveField(string $field, DecisionEventContext $context): mixed
    {
        return match ($field) {
            'activity.amount' => $context->activity?->amount,
            'activity.note' => $context->activity?->note,
            'contract.active' => $context->contract !== null ? (bool) $context->contract->active : null,
            'contract.account.balance_amount' => $this->resolveAccountBalance($context),
            default => null,
        };
    }

    private function resolveAccountBalance(DecisionEventContext $context): mixed
    {
        if (! $context->contract) {
            return null;
        }

        $context->contract->loadMissing('account');

        return $context->contract->account?->balance_amount;
    }

    protected function compare(mixed $actual, string $operator, mixed $expected): bool
    {
        if ($actual === null) {
            return false;
        }

        if (in_array($operator, ['>', '>=', '<', '<='], true)) {
            $actual = (float) $actual;
            $expected = (float) $expected;
        }

        return match ($operator) {
            '=' => $actual == $expected,
            '!=' => $actual != $expected,
            '>' => $actual > $expected,
            '>=' => $actual >= $expected,
            '<' => $actual < $expected,
            '<=' => $actual <= $expected,
            'contains' => str_contains((string) $actual, (string) $expected),
            default => false,
        };
    }

    /**
     * Returns available condition field definitions for the frontend.
     *
     * @return array<int, array{key: string, label: string, type: string}>
     */
    public static function availableFields(): array
    {
        return [
            ['key' => 'activity.amount', 'label' => 'Aktivnost – znesek', 'type' => 'numeric'],
            ['key' => 'activity.note', 'label' => 'Aktivnost – opomba', 'type' => 'string'],
            ['key' => 'contract.active', 'label' => 'Pogodba – aktivna', 'type' => 'boolean'],
            ['key' => 'contract.account.balance_amount', 'label' => 'Račun – stanje', 'type' => 'numeric'],
        ];
    }

    /**
     * Returns available operators grouped by field type.
     *
     * @return array<string, array<int, array{key: string, label: string}>>
     */
    public static function availableOperators(): array
    {
        return [
            'numeric' => [
                ['key' => '=',  'label' => 'je enako'],
                ['key' => '!=', 'label' => 'ni enako'],
                ['key' => '>',  'label' => 'je večje od'],
                ['key' => '>=', 'label' => 'je večje ali enako'],
                ['key' => '<',  'label' => 'je manjše od'],
                ['key' => '<=', 'label' => 'je manjše ali enako'],
            ],
            'string' => [
                ['key' => '=',        'label' => 'je enako'],
                ['key' => '!=',       'label' => 'ni enako'],
                ['key' => 'contains', 'label' => 'vsebuje'],
            ],
            'boolean' => [
                ['key' => '=',  'label' => 'je'],
                ['key' => '!=', 'label' => 'ni'],
            ],
        ];
    }
}
