<?php

declare(strict_types=1);

namespace BotGuard\Services\Detection;

use BotGuard\Core\Database;
use BotGuard\Services\Detection\Contracts\DetectionModuleInterface;
use Throwable;

/**
 * Builds a configured DetectionEngine.
 *
 * The database (module_configs + thresholds) is the source of truth once the
 * app is installed. If the DB is unavailable or unseeded (e.g. the CLI
 * simulator or a unit test), it transparently falls back to the factory
 * defaults in config/detection.php so the engine is always constructable.
 */
final class EngineFactory
{
    public function __construct(private ?Database $db = null)
    {
    }

    public function make(): DetectionEngine
    {
        [$modules, $human, $suspicious, $actions] = $this->loadFromDatabase()
            ?? $this->loadFromConfig();

        return new DetectionEngine($modules, $human, $suspicious, $actions);
    }

    /**
     * @return array{0:array<int,array{key:string,weight:float,module:DetectionModuleInterface}>,1:int,2:int,3:array<string,string>}|null
     */
    private function loadFromDatabase(): ?array
    {
        if ($this->db === null) {
            return null;
        }

        try {
            $rows = $this->db->select(
                'SELECT `module_key`, `class`, `weight` FROM `module_configs` WHERE `enabled` = 1 ORDER BY `sort_order`'
            );

            if ($rows === []) {
                return null;
            }

            $modules = [];
            foreach ($rows as $row) {
                $module = $this->instantiate((string) $row['class']);
                if ($module !== null) {
                    $modules[] = [
                        'key'    => (string) $row['module_key'],
                        'weight' => (float) $row['weight'],
                        'module' => $module,
                    ];
                }
            }

            $bands = $this->db->select('SELECT `band`, `min_score`, `action` FROM `thresholds`');
            $human = 80;
            $suspicious = 60;
            $actions = [];
            foreach ($bands as $band) {
                $actions[(string) $band['band']] = (string) $band['action'];
                if ($band['band'] === 'human') {
                    $human = (int) $band['min_score'];
                } elseif ($band['band'] === 'suspicious') {
                    $suspicious = (int) $band['min_score'];
                }
            }

            if ($modules === []) {
                return null;
            }

            return [$modules, $human, $suspicious, $actions];
        } catch (Throwable) {
            return null; // fall back to config
        }
    }

    /**
     * @return array{0:array<int,array{key:string,weight:float,module:DetectionModuleInterface}>,1:int,2:int,3:array<string,string>}
     */
    private function loadFromConfig(): array
    {
        $modules = [];
        foreach ((array) config('detection.modules', []) as $key => $def) {
            if (empty($def['enabled'])) {
                continue;
            }
            $module = $this->instantiate((string) ($def['class'] ?? ''));
            if ($module !== null) {
                $modules[] = [
                    'key'    => (string) $key,
                    'weight' => (float) ($def['weight'] ?? 10),
                    'module' => $module,
                ];
            }
        }

        return [
            $modules,
            (int) config('detection.thresholds.human', 80),
            (int) config('detection.thresholds.suspicious', 60),
            (array) config('detection.actions', []),
        ];
    }

    private function instantiate(string $class): ?DetectionModuleInterface
    {
        if ($class === '' || ! class_exists($class)) {
            return null;
        }

        $instance = new $class();

        return $instance instanceof DetectionModuleInterface ? $instance : null;
    }
}
