<?php
declare(strict_types=1);

class AutomationRuleEngine
{
    private const MAX_DEPTH = 10;

    private PDO $db;
    private RecipientAutomationService $recipientAutomationService;

    public function __construct(PDO $db, ?RecipientAutomationService $recipientAutomationService = null)
    {
        $this->db = $db;
        $this->recipientAutomationService = $recipientAutomationService ?? new RecipientAutomationService($db);
        $this->recipientAutomationService->setRuleEngine($this);
    }

    public function handleEvent(string $eventType, int $recipientId, int $subjectId, array $context = []): void
    {
        if ($recipientId <= 0 || $subjectId <= 0) {
            return;
        }

        $depth = (int) ($context['depth'] ?? 0);
        if ($depth >= self::MAX_DEPTH) {
            ErrorLogger::logSqlError('automation_rules: max depth reached | context: ' . json_encode(['event_type' => $eventType, 'recipient_id' => $recipientId, 'subject_id' => $subjectId], JSON_UNESCAPED_UNICODE));
            return;
        }

        $eventKey = $eventType . ':' . $recipientId . ':' . $subjectId;
        $eventStack = $context['event_stack'] ?? [];
        if (in_array($eventKey, $eventStack, true)) {
            ErrorLogger::logSqlSuccess('automation_rules: skip recursive event | context: ' . json_encode(['event_key' => $eventKey], JSON_UNESCAPED_UNICODE));
            return;
        }

        try {
            $sql = 'SELECT *
                      FROM automation_rules
                     WHERE is_active = 1
                       AND event_type = ?
                       AND event_tag_id = ?
                  ORDER BY id ASC';
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$eventType, $subjectId]);
            ErrorLogger::logSqlSuccess('automation_rules: load rules | SQL: ' . $sql . ' | params: ' . json_encode([$eventType, $subjectId], JSON_UNESCAPED_UNICODE));
            $rules = $stmt->fetchAll(PDO::FETCH_ASSOC);
        } catch (Throwable $e) {
            ErrorLogger::logSqlError('automation_rules: load rules failed | context: ' . json_encode(['event_type' => $eventType, 'subject_id' => $subjectId], JSON_UNESCAPED_UNICODE) . ' | error: ' . $e->getMessage());
            return;
        }

        if ($rules === []) {
            return;
        }

        $nextContext = $context;
        $nextContext['depth'] = $depth + 1;
        $nextContext['event_stack'] = array_merge($eventStack, [$eventKey]);
        $executedRules = $context['executed_rule_ids'] ?? [];

        foreach ($rules as $rule) {
            $ruleId = (int) ($rule['id'] ?? 0);
            if ($ruleId <= 0) {
                continue;
            }

            if (in_array($ruleId, $executedRules, true)) {
                $this->writeRuleLog($ruleId, $recipientId, $eventType, (string) $subjectId, 'skipped', ['reason' => 'already_executed_in_stack']);
                continue;
            }

            if (!empty($rule['only_once_per_recipient']) && $this->hasExecutedRule($ruleId, $recipientId)) {
                $this->writeRuleLog($ruleId, $recipientId, $eventType, (string) $subjectId, 'skipped', ['reason' => 'only_once_per_recipient']);
                continue;
            }

            try {
                $ruleContext = $nextContext;
                $ruleContext['executed_rule_ids'] = array_merge($executedRules, [$ruleId]);
                $result = $this->executeRule($rule, $recipientId, $ruleContext);
                $status = $result ? 'executed' : 'skipped';
                $this->writeRuleLog($ruleId, $recipientId, $eventType, (string) $subjectId, $status, [
                    'action_type' => $rule['action_type'] ?? null,
                    'action_target_tag_id' => $rule['action_target_tag_id'] ?? null,
                    'action_target_list_id' => $rule['action_target_list_id'] ?? null,
                ]);
            } catch (Throwable $e) {
                $this->writeRuleLog($ruleId, $recipientId, $eventType, (string) $subjectId, 'failed', ['error' => $e->getMessage()]);
                ErrorLogger::logSqlError('automation_rules: execute rule failed | context: ' . json_encode(['rule_id' => $ruleId, 'recipient_id' => $recipientId], JSON_UNESCAPED_UNICODE) . ' | error: ' . $e->getMessage());
            }
        }
    }

    private function executeRule(array $rule, int $recipientId, array $context): bool
    {
        $actionType = (string) ($rule['action_type'] ?? '');

        switch ($actionType) {
            case 'assign_tag':
                return $this->recipientAutomationService->assignTagToRecipient(
                    $recipientId,
                    (int) ($rule['action_target_tag_id'] ?? 0),
                    $context
                );

            case 'remove_tag':
                return $this->recipientAutomationService->removeTagFromRecipient(
                    $recipientId,
                    (int) ($rule['action_target_tag_id'] ?? 0),
                    $context
                );

            case 'subscribe_list':
                return $this->recipientAutomationService->subscribeRecipientToList(
                    $recipientId,
                    (int) ($rule['action_target_list_id'] ?? 0)
                );

            case 'unsubscribe_list':
                return $this->recipientAutomationService->unsubscribeRecipientFromList(
                    $recipientId,
                    (int) ($rule['action_target_list_id'] ?? 0)
                );

            default:
                throw new RuntimeException('Unbekannter action_type: ' . $actionType);
        }
    }

    private function hasExecutedRule(int $ruleId, int $recipientId): bool
    {
        $sql = 'SELECT 1
                  FROM automation_rule_logs
                 WHERE rule_id = ?
                   AND recipient_id = ?
                   AND status = ?
                 LIMIT 1';
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$ruleId, $recipientId, 'executed']);
        ErrorLogger::logSqlSuccess('automation_rules: check prior execution | SQL: ' . $sql . ' | params: ' . json_encode([$ruleId, $recipientId, 'executed'], JSON_UNESCAPED_UNICODE));

        return (bool) $stmt->fetchColumn();
    }

    private function writeRuleLog(int $ruleId, int $recipientId, string $eventType, string $eventReference, string $status, array $details = []): void
    {
        try {
            $sql = 'INSERT INTO automation_rule_logs (rule_id, recipient_id, event_type, event_reference, status, details, created_at)
                    VALUES (?, ?, ?, ?, ?, ?, NOW())';
            $stmt = $this->db->prepare($sql);
            $stmt->execute([
                $ruleId,
                $recipientId,
                $eventType,
                $eventReference,
                $status,
                $details === [] ? null : json_encode($details, JSON_UNESCAPED_UNICODE),
            ]);
            ErrorLogger::logSqlSuccess('automation_rules: write rule log | SQL: ' . $sql . ' | params: ' . json_encode([$ruleId, $recipientId, $eventType, $eventReference, $status], JSON_UNESCAPED_UNICODE));
        } catch (Throwable $e) {
            ErrorLogger::logSqlError('automation_rules: write log failed | context: ' . json_encode(['rule_id' => $ruleId, 'recipient_id' => $recipientId, 'status' => $status], JSON_UNESCAPED_UNICODE) . ' | error: ' . $e->getMessage());
        }
    }
}
