<?php

declare(strict_types=1);

namespace App\Framework\Scheduler\Services;

use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Scheduler\Contracts\ScheduleInterface;
use App\Framework\Scheduler\ValueObjects\ScheduledTask;
use App\Framework\Scheduler\ValueObjects\TaskExecutionResult;

/**
 * Central scheduler service for managing scheduled tasks
 */
final class SchedulerService
{
    /** @var array<string, ScheduledTask> */
    private array $scheduledTasks = [];

    public function __construct(
        private readonly Logger $logger
    ) {
    }

    /**
     * Register a scheduled task
     */
    public function schedule(string $taskId, ScheduleInterface $schedule, callable $task): void
    {
        $this->scheduledTasks[$taskId] = new ScheduledTask(
            id: $taskId,
            schedule: $schedule,
            task: $task,
            nextExecution: $schedule->getNextExecution()
        );

        $this->logger->info('Task scheduled', LogContext::withData([
            'task_id' => $taskId,
            'schedule_type' => $schedule->getType(),
            'description' => $schedule->getDescription(),
            'next_execution' => $this->scheduledTasks[$taskId]->nextExecution?->format('c'),
        ]));
    }

    /**
     * Unschedule a task
     */
    public function unschedule(string $taskId): bool
    {
        if (! isset($this->scheduledTasks[$taskId])) {
            return false;
        }

        unset($this->scheduledTasks[$taskId]);

        $this->logger->info('Task unscheduled', LogContext::withData([
            'task_id' => $taskId,
        ]));

        return true;
    }

    /**
     * Get all scheduled tasks
     */
    public function getScheduledTasks(): array
    {
        return $this->scheduledTasks;
    }

    /**
     * Get a specific scheduled task
     */
    public function getTask(string $taskId): ?ScheduledTask
    {
        return $this->scheduledTasks[$taskId] ?? null;
    }

    /**
     * Get tasks that should execute now
     */
    public function getDueTasks(?Timestamp $at = null): array
    {
        $at = $at ?? Timestamp::now();
        $dueTasks = [];

        foreach ($this->scheduledTasks as $task) {
            if ($task->isDue($at)) {
                $dueTasks[] = $task;
            }
        }

        return $dueTasks;
    }

    /**
     * Execute all due tasks
     */
    public function executeDueTasks(?Timestamp $at = null): array
    {
        $dueTasks = $this->getDueTasks($at);
        $results = [];

        foreach ($dueTasks as $task) {
            $results[] = $this->executeTask($task);
        }

        return $results;
    }

    /**
     * Execute a specific task and update its next execution time
     */
    public function executeTask(ScheduledTask $task): TaskExecutionResult
    {
        $startTime = Timestamp::now();

        try {
            $this->logger->info('Executing scheduled task', LogContext::withData([
                'task_id' => $task->id,
                'schedule_type' => $task->schedule->getType(),
            ]));

            // Execute the task
            $result = ($task->task)();

            // Update next execution time
            $nextExecution = $task->schedule->getNextExecution();
            $this->scheduledTasks[$task->id] = $task->withNextExecution($nextExecution);

            $duration = Timestamp::now()->toTimestamp() - $startTime->toTimestamp();

            $this->logger->info('Scheduled task completed successfully', LogContext::withData([
                'task_id' => $task->id,
                'execution_time_seconds' => $duration,
                'next_execution' => $nextExecution?->format('c'),
            ]));

            return TaskExecutionResult::success(
                taskId: $task->id,
                executionTime: $duration,
                result: $result,
                nextExecution: $nextExecution
            );

        } catch (\Throwable $e) {
            $duration = Timestamp::now()->toTimestamp() - $startTime->toTimestamp();

            $this->logger->error('Scheduled task failed', LogContext::withData([
                'task_id' => $task->id,
                'error' => $e->getMessage(),
                'execution_time_seconds' => $duration,
            ]));

            // Still update next execution time for recurring tasks
            $nextExecution = $task->schedule->getNextExecution();
            if ($nextExecution) {
                $this->scheduledTasks[$task->id] = $task->withNextExecution($nextExecution);
            }

            return TaskExecutionResult::failure(
                taskId: $task->id,
                executionTime: $duration,
                error: $e,
                nextExecution: $nextExecution
            );
        }
    }

    /**
     * Check if a task exists
     */
    public function hasTask(string $taskId): bool
    {
        return isset($this->scheduledTasks[$taskId]);
    }

    /**
     * Get the next scheduled execution time across all tasks
     */
    public function getNextExecution(): ?Timestamp
    {
        $nextExecution = null;

        foreach ($this->scheduledTasks as $task) {
            if ($task->nextExecution && (! $nextExecution || $task->nextExecution->isBefore($nextExecution))) {
                $nextExecution = $task->nextExecution;
            }
        }

        return $nextExecution;
    }

    /**
     * Get scheduler statistics
     */
    public function getStats(): array
    {
        $stats = [
            'total_tasks' => count($this->scheduledTasks),
            'due_tasks' => count($this->getDueTasks()),
            'next_execution' => $this->getNextExecution()?->format('c'),
            'schedule_types' => [],
        ];

        foreach ($this->scheduledTasks as $task) {
            $type = $task->schedule->getType();
            $stats['schedule_types'][$type] = ($stats['schedule_types'][$type] ?? 0) + 1;
        }

        return $stats;
    }
}
