<?php

namespace EasyLinkAutomations\Providers;

if (!defined('ABSPATH')) {
    exit;
}

use EasyLinkAutomations\Actions\ActionRegistry;
use EasyLinkAutomations\Actions\Contracts\ActionInterface;
use EasyLinkAutomations\Auth\Contracts\PlatformAuthInterface;
use EasyLinkAutomations\Auth\PlatformAuthRegistry;
use EasyLinkAutomations\Automation\AutomationRunner;
use EasyLinkAutomations\Models\ExecutionLog;
use EasyLinkAutomations\Triggers\Contracts\TriggerInterface;
use EasyLinkAutomations\Triggers\TriggerRegistry;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use ReflectionClass;
use ReflectionException;

/**
 * Registers all triggers and actions, and hooks them into WordPress.
 */
final class AutomationProvider
{
    private AutomationRunner $runner;

    public function __construct()
    {
        $this->runner = new AutomationRunner();
    }

    /**
     * Boot the provider.
     */
    public function boot(): void
    {
        $this->registerPlatformAuths();
        $this->registerTriggers();
        $this->registerActions();
        $this->hookTriggers();
        $this->registerCronHandlers();
    }

    /**
     * Register all platform auth definitions.
     */
    private function registerPlatformAuths(): void
    {
        $classes = $this->discoverClasses(
            __DIR__ . '/../Auth/Platforms',
            'EasyLinkAutomations\\Auth\\Platforms',
            PlatformAuthInterface::class
        );

        foreach ($classes as $class) {
            PlatformAuthRegistry::register($class);
        }

        // Allow plugins to register custom platform auths
        do_action('easylink_automations_register_platform_auths', PlatformAuthRegistry::class);
    }

    /**
     * Register all available triggers.
     */
    private function registerTriggers(): void
    {
        $classes = $this->discoverClasses(
            __DIR__ . '/../Triggers',
            'EasyLinkAutomations\\Triggers',
            TriggerInterface::class
        );

        foreach ($classes as $class) {
            TriggerRegistry::register($class);
        }

        // Allow plugins to register custom triggers
        do_action('easylink_automations_register_triggers', TriggerRegistry::class);
    }

    /**
     * Register all available actions.
     */
    private function registerActions(): void
    {
        $classes = $this->discoverClasses(
            __DIR__ . '/../Actions',
            'EasyLinkAutomations\\Actions',
            ActionInterface::class
        );

        foreach ($classes as $class) {
            ActionRegistry::register($class);
        }

        // Allow plugins to register custom actions
        do_action('easylink_automations_register_actions', ActionRegistry::class);
    }

    /**
     * Hook triggers into WordPress actions.
     */
    private function hookTriggers(): void
    {
        $triggers = TriggerRegistry::all();

        foreach ($triggers as $trigger) {
            $trigger->register(function (array $data) use ($trigger) {
                $this->runner->run($trigger::getSlug(), $data);
            });
        }
    }

    /**
     * Register cron handlers for delayed executions and log cleanup.
     */
    private function registerCronHandlers(): void
    {
        add_action('easylink_automations_delayed_execution', [$this, 'handleDelayedExecution']);

        // Log cleanup cron
        $cleanupHook = \EasyLinkAutomations\Config::withPrefix('cron_cleanup');
        add_action($cleanupHook, [self::class, 'handleLogCleanup']);

        if (!wp_next_scheduled($cleanupHook)) {
            wp_schedule_event(time(), 'daily', $cleanupHook);
        }
    }

    /**
     * Handle scheduled log cleanup based on settings.
     */
    public static function handleLogCleanup(): void
    {
        $settings = \EasyLinkAutomations\Config::getOption('settings', []);
        $keepLogs = $settings['keep_logs'] ?? true;

        if (!$keepLogs) {
            // Logging is disabled — delete all logs
            ExecutionLog::deleteAll();
            return;
        }

        $retentionDays = (int) ($settings['log_retention_days'] ?? 30);
        ExecutionLog::cleanup($retentionDays);
    }

    /**
     * Handle delayed execution from WP Cron.
     */
    public function handleDelayedExecution(array $payload): void
    {
        $automationId = $payload['automation_id'] ?? 0;
        $actions      = $payload['actions'] ?? [];
        $data         = $payload['data'] ?? [];
        $logId        = $payload['log_id'] ?? 0;

        $results        = [];
        $overallSuccess = true;
        $delayPending   = false;
        $startTime      = microtime(true);

        foreach ($actions as $index => $actionRow) {
            $actionSlug = $actionRow['action_slug'];
            $action     = ActionRegistry::get($actionSlug);

            if (!$action) {
                $results[] = [
                    'action'  => $actionSlug,
                    'success' => false,
                    'message' => __('Action not found', 'easylink-automations'),
                ];
                $overallSuccess = false;
                break;
            }

            try {
                $config = json_decode($actionRow['config'], true) ?? [];
                $result = $action->execute($data, $config);

                $results[] = [
                    'action'  => $actionSlug,
                    'success' => $result['success'],
                    'message' => $result['message'],
                    'data'    => $result['data'] ?? null,
                ];

                if (!$result['success']) {
                    $overallSuccess = false;
                    break;
                }

                // Handle nested delays
                if (!empty($result['requires_delay'])) {
                    $this->scheduleDelayedContinuation(
                        $automationId,
                        $actions,
                        $index + 1,
                        $data,
                        $result['delay_seconds']
                    );
                    $delayPending = true;
                    break;
                }

                // Merge output data
                if (!empty($result['data'])) {
                    $data = array_merge($data, $result['data']);
                }
            } catch (\Throwable $e) {
                $results[] = [
                    'action'  => $actionSlug,
                    'success' => false,
                    'message' => $e->getMessage(),
                ];
                $overallSuccess = false;
                break;
            }
        }

        $status = $delayPending ? 'running' : ($overallSuccess ? 'success' : 'failed');
        $now    = current_time('mysql');

        $settings = \EasyLinkAutomations\Config::getOption('settings', []);
        $keepLogs = $settings['keep_logs'] ?? true;

        // Update the original log entry instead of creating a new one.
        if ($logId && $keepLogs) {
            ExecutionLog::update($logId, [
                'status'        => $status,
                'result_data'   => wp_json_encode([
                    'actions' => $results,
                ]),
                'error_message' => $status === 'failed' ? ($results[count($results) - 1]['message'] ?? null) : null,
                'completed_at'  => $delayPending ? null : $now,
            ]);
        } elseif (!$logId && $keepLogs) {
            // Fallback: create a new entry if no log_id was passed.
            ExecutionLog::create([
                'automation_id' => $automationId,
                'status'        => $status,
                'trigger_data'  => wp_json_encode(['delayed_continuation' => true]),
                'result_data'   => wp_json_encode([
                    'actions' => $results,
                ]),
                'error_message' => $status === 'failed' ? ($results[count($results) - 1]['message'] ?? null) : null,
                'started_at'    => $now,
                'completed_at'  => $delayPending ? null : $now,
            ]);
        }

        // Send failure notification email
        if ($status === 'failed' && !empty($settings['email_notifications'])) {
            $email = !empty($settings['notification_email']) ? $settings['notification_email'] : get_option('admin_email');
            if ($email) {
                $automation = \EasyLinkAutomations\Models\Automation::find($automationId);
                $title      = $automation['title'] ?? 'Untitled';
                $lastError  = '';
                foreach (array_reverse($results) as $r) {
                    if (!($r['success'] ?? true)) {
                        $lastError = $r['message'] ?? 'Unknown error';
                        break;
                    }
                }
                wp_mail(
                    $email,
                    sprintf('[EasyLink Automations] Automation failed: %s', $title),
                    sprintf(
                        "The automation \"%s\" (ID: %d) has failed after a delayed execution.\n\nError: %s\n\nTime: %s\n\nYou can view the execution logs in your WordPress admin panel.",
                        $title,
                        $automationId,
                        $lastError,
                        $now
                    )
                );
            }
        }
    }

    /**
     * Schedule continuation for nested delays.
     */
    private function scheduleDelayedContinuation(
        int $automationId,
        array $actions,
        int $startIndex,
        array $data,
        int $delaySeconds
    ): void {
        $payload = [
            'automation_id' => $automationId,
            'actions'       => array_slice($actions, $startIndex),
            'data'          => $data,
        ];

        wp_schedule_single_event(
            time() + $delaySeconds,
            'easylink_automations_delayed_execution',
            [$payload]
        );
    }

    /**
     * @return array<int, class-string>
     */
    private function discoverClasses(string $directory, string $namespaceRoot, string $requiredInterface): array
    {
        if (!is_dir($directory)) {
            return [];
        }

        $classes = [];
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS)
        );

        foreach ($iterator as $file) {
            if (!$file->isFile() || $file->getExtension() !== 'php') {
                continue;
            }

            $filename = $file->getFilename();
            if (str_ends_with($filename, 'Helper.php')) {
                continue;
            }

            $path = $file->getPathname();
            $relativePath = substr($path, strlen($directory) + 1);
            if ($relativePath === false) {
                continue;
            }

            $classPath = str_replace(['/', '\\'], '\\', $relativePath);
            $classPath = preg_replace('/\\.php$/', '', $classPath);
            if (!is_string($classPath) || $classPath === '') {
                continue;
            }

            $class = $namespaceRoot . '\\' . $classPath;

            if (!class_exists($class) || !is_subclass_of($class, $requiredInterface)) {
                continue;
            }

            try {
                $reflection = new ReflectionClass($class);
                if ($reflection->isAbstract()) {
                    continue;
                }
            } catch (ReflectionException) {
                continue;
            }

            $classes[] = $class;
        }

        sort($classes);

        return $classes;
    }
}
