<?php

namespace App\Console\Commands;

use App\Models\ScheduledTask;
use App\Models\SubScheduledTask;
use App\Models\Log;
use App\Models\Register;
use App\Jobs\CheckConnectionJob;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use PhpMqtt\Client\Facades\MQTT;
use Exception;

class ProcessScheduledTasks extends Command
{
    protected $signature = 'tasks:process';
    protected $description = 'Process scheduled tasks based on their timing configuration';

   public function enrichSubtask($subtask)
    {
        $subtask->load('device.connection.broker');

        if ($subtask->device) {
            $subtask->connection = $subtask->device->connection;
            $subtask->broker = $subtask->device->connection->broker;
        }

        return $subtask;
    }

    public function handle()
    {
        $now = Carbon::now();
        $this->info("Processing scheduled tasks at: {$now->format('Y-m-d H:i:s')}");

        $processed = 0;
        $skipped = 0;
        $errors = 0;

        // Get all active scheduled tasks
        $tasks = ScheduledTask::with(['subTasks' => function($query) {
            $query->where('active', true)
                ->with(['device', 'register', 'command', 'scheduledTask']);
        }])->where('active', true)->get();
	
	foreach ( $tasks as $task ) {
            foreach ( $task->subTasks as $subtask ) {
                $subtask = $this->enrichSubtask($subtask);

                dd($subtask->broker);

            }
        }

	foreach ($tasks as $task) {
            if ($this->shouldRunTask($task, $now)) {
                $this->info("Executing task: {$task->name}");

                foreach ($task->subTasks as $subTask) {
                    try {
                        if ($this->shouldRunSubTask($subTask, $now)) {
                            $this->executeSubTask($subTask);
                            $processed++;
                        } else {
                            $skipped++;
                            $this->info("Skipping SubTask {$subTask->uuid} (not in execution time)");
                        }
                    } catch (Exception $e) {
                        $errors++;
                        $this->error("Error processing SubTask {$subTask->uuid}: " . $e->getMessage());
                    }
                }
            } else {
                $this->info("Skipping task {$task->uuid} (not in scheduled time window)");
            }
        }

        // Dispatch connection check job
        try {
            CheckConnectionJob::dispatch()->onConnection('database');
            $this->info("CheckConnectionJob dispatched");
        } catch (Exception $e) {
            $this->error('Error dispatching CheckConnectionJob: ' . $e->getMessage());
        }

        $this->info("Completed: {$processed} executed, {$skipped} skipped, {$errors} errors");
    }

    private function shouldRunTask(ScheduledTask $task, Carbon $now): bool
    {
        // Check weekday
        $weekdays = json_decode($task->week_days) ?? [];
        $today = strtolower($now->format('D'));

        if (!in_array($today, $weekdays)) {
            return false;
        }

        // Check time window
        $startTime = Carbon::parse($task->start_time);
        $endTime = $task->end_time ? Carbon::parse($task->end_time) : null;

        if (!$now->between($startTime, $endTime ?? $now->copy()->addMinute())) {
            return false;
        }

        return true;
    }

    private function shouldRunSubTask($subTask, Carbon $now): bool
    {
        // بررسی checksum
        if ($subTask->checksum) {
            $register = Register::find($subTask->checksum_register_uuid);
            if ($register && $register->value == $subTask->checksum_register_value) {
                return false; // checksum شرط را برقرار نکرده
            }
        }

        // اگر از زمان‌بندی کاستوم استفاده می‌کند
        if ($subTask->run_time === 'custom' && $subTask->custom_run_time_type !== 'off') {
            return $this->shouldRunCustomSchedule($subTask, $now);
        }

        // برای انواع دیگر run_time
        return $this->shouldRunStandardSchedule($subTask, $now);
    }

    private function shouldRunCustomSchedule($subTask, Carbon $now): bool
    {
        $lastRun = $subTask->last_run_at ? Carbon::parse($subTask->last_run_at) : null;

        if (!$lastRun) {
            return true; // اولین اجرا
        }

        $interval = $subTask->custom_run_time_value;
        $type = $subTask->custom_run_time_type;

        switch ($type) {
            case 'second':
                return $lastRun->diffInSeconds($now) >= $interval;
            case 'minute':
                return $lastRun->diffInMinutes($now) >= $interval;
            case 'hour':
                return $lastRun->diffInHours($now) >= $interval;
            case 'day':
                return $lastRun->diffInDays($now) >= $interval;
            case 'week':
                return $lastRun->diffInWeeks($now) >= $interval;
            case 'month':
                return $lastRun->diffInMonths($now) >= $interval;
            default:
                return false;
        }
    }

    private function shouldRunStandardSchedule($subTask, Carbon $now): bool
    {
        $lastRun = $subTask->last_run_at ? Carbon::parse($subTask->last_run_at) : null;

        switch ($subTask->run_time) {
            case 'start':
                return !$lastRun; // فقط یک بار در شروع
            case 'hourly':
                return !$lastRun || $lastRun->diffInHours($now) >= 1;
            case 'daily':
                return !$lastRun || $lastRun->diffInDays($now) >= 1;
            case 'weekly':
                return !$lastRun || $lastRun->diffInWeeks($now) >= 1;
            case 'monthly':
                return !$lastRun || $lastRun->diffInMonths($now) >= 1;
            case 'end':
                // منطق خاص برای اجرا در پایان
                return $this->shouldRunAtEnd($subTask, $now);
            default:
                return true; // برای انواع دیگر مثل 'count', 'register' و ...
        }
    }

    private function shouldRunAtEnd($subTask, Carbon $now): bool
    {
        // اینجا می‌توانید منطق خاص برای اجرا در پایان را پیاده‌سازی کنید
        // مثلاً بررسی کنید آیا زمان پایان تسک اصلی رسیده یا نه
        $task = $subTask->scheduledTask;
        if ($task->end_time) {
            $endTime = Carbon::parse($task->end_time);
            return $now->greaterThanOrEqualTo($endTime);
        }
        return false;
    }

    private function executeSubTask($subTask)
    {
        try {
            $run = !$subTask->checksum ||
                Register::find($subTask->checksum_register_uuid)?->value != $subTask->checksum_register_value;

            if (!$run) {
                $this->info("Skipping SubTask {$subTask->uuid} (checksum condition not met)");
                return;
            }

            if ($subTask->type == 'command') {
                $this->executeCommandSubTask($subTask);
            } else {
                $this->executeLogSubTask($subTask);
            }

            // به روزرسانی زمان آخرین اجرا و تعداد اجراها
            $subTask->update([
                'last_run_at' => Carbon::now(),
                'ran' => $subTask->ran + 1,
                'status' => 'done'
            ]);

            $this->info("✓ Executed SubTask {$subTask->title} ({$subTask->uuid})");

        } catch (Exception $e) {
            $subTask->update(['status' => 'failed']);
            throw $e;
        }
    }

    private function executeCommandSubTask($subTask)
    {
        $mqtt = null;

        try {
            $mqtt = MQTT::connection();
            $this->info("MQTT connection established for SubTask {$subTask->uuid}");

            if (is_null($subTask->Command)) {
                $message = $subTask->value;
                $isJson = strtolower($subTask->register->Pattern->type) === 'json';
            } else {
                $message = $subTask->Command->command;
                $isJson = strtolower($subTask->Command->type) === 'json';
            }

            if (is_null($message)) {
                throw new Exception("Message is null for SubTask {$subTask->uuid}");
            }

            $message = (string)$message;

            $this->info("Sending MQTT command to device {$subTask->device->uuid} on topic {$subTask->device->mqtt_topic}");
            $response = $this->publishAndGetResponse($mqtt, $subTask->device->mqtt_topic, $message, $isJson);

            if ($response !== null) {
                $this->registerLog($subTask, $response, $isJson);
            } else {
                $this->warn("No response received for SubTask {$subTask->uuid}");
            }

        } catch (Exception $e) {
            $this->error("MQTT communication failed for SubTask {$subTask->uuid}: " . $e->getMessage());
            throw $e;
        } finally {
            if ($mqtt) {
                try {
                    $mqtt->disconnect();
                } catch (Exception $e) {
                    $this->warn("Error disconnecting MQTT: " . $e->getMessage());
                }
            }
        }
    }

    private function executeLogSubTask($subTask)
    {
        try {
            Log::create([
                'loggable_type' => Register::class,
                'loggable_uuid' => $subTask->register_uuid,
                'title' => $subTask->register->title,
                'value' => $subTask->register->value,
            ]);

            if (function_exists('CheckForCharts')) {
                CheckForCharts($subTask->register);
            }

            $this->info("Log created for SubTask {$subTask->uuid}");

        } catch (Exception $e) {
            $this->error("Database operation failed for SubTask {$subTask->uuid}: " . $e->getMessage());
            throw $e;
        }
    }

    /**
     * ارسال پیام MQTT و دریافت پاسخ (کپی از متد اصلی)
     */
    private function publishAndGetResponse($mqtt, string $topic, string $message, bool $parseJSON = false, int $timeoutSeconds = 6)
    {
        $response = null;
        $receivedTopic = null;

        $topic = trim($topic);
        $message = trim($message);

        $topicsToSubscribe = [$topic];
        if (!str_ends_with($topic, '/#')) {
            $topicsToSubscribe[] = rtrim($topic, '/') . '/#';
        }
        $topicsToSubscribe[] = 'responses/#';
        $topicsToSubscribe = array_values(array_unique($topicsToSubscribe));

        $publishedAt = null;
        $ignoredEcho = false;

        $handler = function (string $t, string $msg) use (&$response, &$receivedTopic, &$publishedAt, &$ignoredEcho, $message, $mqtt, &$topicsToSubscribe) {
            $t = trim($t);
            $msg = trim($msg);
            $now = microtime(true);

            if ($publishedAt !== null) {
                $delta = $now - $publishedAt;
                if ($msg === $message && $delta < 0.6) {
                    $ignoredEcho = true;
                    $this->info("Ignored echo/retained message (delta={$delta}s, topic={$t})");
                    return;
                }
            }

            if (is_null($response)) {
                $response = $msg;
                $receivedTopic = $t;
                $this->info("Received response from {$t}: {$msg}");

                // Unsubscribe from all topics
                foreach ($topicsToSubscribe as $sub) {
                    try {
                        $mqtt->unsubscribe($sub);
                    } catch (\Throwable $e) {
                        // Ignore unsubscribe errors
                    }
                }
            }
        };

        try {
            // Subscribe to topics
            foreach ($topicsToSubscribe as $subTopic) {
                try {
                    $mqtt->subscribe($subTopic, $handler, 0);
                    $this->info("Subscribed to: {$subTopic}");
                } catch (\Throwable $e) {
                    $this->warn("Subscribe failed for {$subTopic}: " . $e->getMessage());
                    throw new Exception("MQTT Subscribe failed: " . $e->getMessage());
                }
            }

            // Publish message
            $mqtt->publish($topic, $message, 0);
            $publishedAt = microtime(true);
            $this->info("Published to {$topic}: {$message}");

            // Wait for response with timeout
            $deadline = microtime(true) + $timeoutSeconds;
            $loopStartTime = microtime(true);

            while (is_null($response) && microtime(true) < $deadline) {
                $mqtt->loopOnce($loopStartTime, false, 50_000);
                usleep(50_000);
            }

            // Cleanup subscriptions
            foreach ($topicsToSubscribe as $subTopic) {
                try {
                    $mqtt->unsubscribe($subTopic);
                } catch (\Throwable $_) {
                    // Ignore errors
                }
            }

            if (is_null($response)) {
                if ($ignoredEcho) {
                    $this->warn("Ignored echo but no real response received within {$timeoutSeconds}s.");
                } else {
                    $this->warn("No response received on topic {$topic} within {$timeoutSeconds}s.");
                }
                return null;
            }

            if ($parseJSON) {
                $decoded = json_decode($response, true);
                if (json_last_error() === JSON_ERROR_NONE) {
                    return $decoded;
                }
                $this->warn("Invalid JSON received, returning raw string.");
            }

            return $response;

        } catch (\Throwable $e) {
            // Cleanup on error
            foreach ($topicsToSubscribe as $subTopic) {
                try {
                    $mqtt->unsubscribe($subTopic);
                } catch (\Throwable $_) {
                    // Ignore errors
                }
            }
            $this->error('Error in publishAndGetResponse: ' . $e->getMessage());
            throw $e;
        }
    }

    /**
     * ثبت لاگ‌های مربوط به یک SubTask (کپی از متد اصلی)
     */
    private function registerLog($subTask, $response, bool $parseJSON = false)
    {
        if (is_string($response)) {
            $trimmed = trim($response);
            $looksLikeJson = $trimmed !== '' && (str_starts_with($trimmed, '{') || str_starts_with($trimmed, '['));

            if ($parseJSON || $looksLikeJson) {
                $decoded = json_decode($response, true);
                if (json_last_error() === JSON_ERROR_NONE) {
                    $response = $decoded;
                } else {
                    if ($parseJSON) {
                        $this->warn("Invalid JSON string for SubTask {$subTask->uuid}: {$response}");
                        return null;
                    }
                }
            }
        }

        if (!is_array($response)) {
            $this->warn("Response is not array for SubTask {$subTask->uuid}: " . (string)$response);
            return null;
        }

        // گرفتن رجیسترهای مرتبط
        $registers = collect();
        if (!empty($subTask->register_uuid)) {
            $r = Register::where('uuid', $subTask->register_uuid)->first();
            if ($r) $registers->push($r);
        }

        if ($registers->isEmpty()) {
            $this->warn("No registers defined for SubTask {$subTask->uuid}");
            return null;
        }

        $registerMap = $registers->mapWithKeys(function ($reg) {
            return [(string)$reg->key => $reg];
        });

        $createdLogs = [];

        // نرمال‌سازی کلیدهای response
        $normalizedResponse = [];
        foreach ($response as $k => $v) {
            $normalizedResponse[(string)$k] = $v;
        }

        $throttleSeconds = (int)env('REGISTER_LOG_THROTTLE', 60);

        foreach ($normalizedResponse as $key => $value) {
            if (!isset($registerMap[$key])) {
                $this->info("Skipping key '{$key}' (not part of SubTask {$subTask->uuid})");
                continue;
            }

            $register = $registerMap[$key];

            try {
                DB::transaction(function () use (&$createdLogs, $register, $subTask, $key, $value, $throttleSeconds) {
                    $lockedRegister = Register::where('uuid', $register->uuid)->lockForUpdate()->first();

                    if (!$lockedRegister) {
                        $this->warn("Register {$register->uuid} not found during locking.");
                        return;
                    }

                    $lastLog = Log::where('loggable_type', Register::class)
                        ->where('loggable_uuid', $lockedRegister->uuid)
                        ->orderBy('created_at', 'desc')
                        ->first();

                    if ($lastLog && $lastLog->created_at->gt(now()->subSeconds($throttleSeconds))) {
                        $this->info("Skipping register {$lockedRegister->uuid} (last log at {$lastLog->created_at->toDateTimeString()}, throttle={$throttleSeconds}s).");
                        return;
                    }

                    try {
                        if (is_array($value) || is_object($value)) {
                            $storeValue = json_encode($value, JSON_THROW_ON_ERROR);
                        } else {
                            $storeValue = (string)$value;
                        }
                    } catch (\Throwable $e) {
                        $this->error("Failed to encode value for register {$lockedRegister->uuid}: " . $e->getMessage());
                        $storeValue = (string)$value;
                    }

                    try {
                        $lockedRegister->value = $storeValue;
                        $lockedRegister->save();
                    } catch (\Throwable $e) {
                        $this->error("Failed to save register {$lockedRegister->uuid}: " . $e->getMessage());
                        return;
                    }

                    $device = $lockedRegister->device ?? null;

                    $taskName = $subTask->scheduledTask->name ?? 'N/A';
                    $subtaskName = $subTask->title ?? 'N/A';
                    $deviceName = $device?->name ?? 'N/A';
                    $registerName = $lockedRegister->title ?? 'N/A';
                    $title = implode('/', [$taskName, $subtaskName, $deviceName, $registerName]);

                    $log = Log::create([
                        'loggable_type' => Register::class,
                        'loggable_uuid' => $lockedRegister->uuid,
                        'title' => $title,
                        'value' => $storeValue,
                    ]);

                    $createdLogs[] = $log;
                    $this->info("Saved log for register {$lockedRegister->uuid} (key={$key}) => {$storeValue}");
                }, 5);
            } catch (\Throwable $e) {
                $this->error("Transaction error for register {$register->uuid}: " . $e->getMessage());
            }
        }

        return $createdLogs;
    }
}
