<?php
declare(strict_types=1);

namespace App\Modules\Automation;

use PDO;
use Throwable;

final class OrderStatusAgedService
{
    private const MAX_ORDERS_PER_RULE = 100;

    public function __construct(
        private readonly AutomationRepository $repository,
        private readonly AutomationService $automation,
        private readonly PDO $db
    ) {
    }

    public function scan(): int
    {
        $rules = $this->repository->findActiveByEvent('order.status_aged');
        if ($rules === []) {
            return 0;
        }

        $totalTriggered = 0;

        foreach ($rules as $rule) {
            try {
                $totalTriggered += $this->processRule($rule);
            } catch (Throwable) {
                // Blad jednej reguly nie blokuje kolejnych
            }
        }

        return $totalTriggered;
    }

    /**
     * @param array<string, mixed> $rule
     */
    private function processRule(array $rule): int
    {
        $conditions = is_array($rule['conditions'] ?? null) ? $rule['conditions'] : [];
        $statusCodes = $this->extractStatusCodes($conditions);
        $days = $this->extractDays($conditions);

        if ($statusCodes === [] || $days < 1) {
            return 0;
        }

        $orders = $this->findAgedOrders($statusCodes, $days);
        $triggered = 0;

        foreach ($orders as $order) {
            $orderId = (int) ($order['id'] ?? 0);
            if ($orderId <= 0) {
                continue;
            }

            try {
                $currentStatus = strtolower(trim((string) ($order['external_status_id'] ?? '')));
                $lastChanged = (string) ($order['last_changed'] ?? '');
                $actualDays = $lastChanged !== '' ? $this->daysSince($lastChanged) : $days;

                $this->automation->trigger('order.status_aged', $orderId, [
                    'current_status' => $currentStatus,
                    'days_in_status' => $actualDays,
                    'status_changed_at' => $lastChanged,
                ]);
                $triggered++;
            } catch (Throwable) {
                // Blad jednego zamowienia nie blokuje kolejnych
            }
        }

        return $triggered;
    }

    /**
     * @param list<array<string, mixed>> $conditions
     * @return list<string>
     */
    private function extractStatusCodes(array $conditions): array
    {
        foreach ($conditions as $condition) {
            $type = (string) ($condition['condition_type'] ?? '');
            $value = is_array($condition['condition_value'] ?? null) ? $condition['condition_value'] : [];

            if ($type === 'order_status') {
                $codes = is_array($value['order_status_codes'] ?? null) ? $value['order_status_codes'] : [];
                return array_values(array_filter(
                    array_map(static fn (mixed $c): string => strtolower(trim((string) $c)), $codes),
                    static fn (string $c): bool => $c !== ''
                ));
            }
        }

        return [];
    }

    /**
     * @param list<array<string, mixed>> $conditions
     */
    private function extractDays(array $conditions): int
    {
        foreach ($conditions as $condition) {
            $type = (string) ($condition['condition_type'] ?? '');
            $value = is_array($condition['condition_value'] ?? null) ? $condition['condition_value'] : [];

            if ($type === 'days_in_status') {
                return max(0, (int) ($value['days'] ?? 0));
            }
        }

        return 0;
    }

    /**
     * @param list<string> $statusCodes
     * @return list<array<string, mixed>>
     */
    private function findAgedOrders(array $statusCodes, int $days): array
    {
        if ($statusCodes === [] || $days < 1) {
            return [];
        }

        $placeholders = implode(', ', array_fill(0, count($statusCodes), '?'));

        $sql = "SELECT o.id, o.external_status_id, MAX(h.changed_at) AS last_changed
                FROM orders o
                INNER JOIN order_status_history h ON h.order_id = o.id
                    AND LOWER(h.to_status_id) = LOWER(o.external_status_id)
                WHERE LOWER(COALESCE(o.external_status_id, '')) IN ({$placeholders})
                GROUP BY o.id, o.external_status_id
                HAVING MAX(h.changed_at) <= DATE_SUB(NOW(), INTERVAL ? DAY)
                LIMIT " . self::MAX_ORDERS_PER_RULE;

        try {
            $stmt = $this->db->prepare($sql);
            $params = $statusCodes;
            $params[] = $days;
            $stmt->execute($params);

            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
            return is_array($rows) ? $rows : [];
        } catch (Throwable) {
            return [];
        }
    }

    private function daysSince(string $datetime): int
    {
        $timestamp = strtotime($datetime);
        if ($timestamp === false) {
            return 0;
        }

        $diff = time() - $timestamp;
        return max(0, (int) floor($diff / 86400));
    }
}
