<?php

// Register Helpers
$this->helpers['webhooks'] = 'Webhooks\\Helper\\Webhooks';

// webhooks api
$this->module('webhooks')->extend([

    'execute' => function(array $webhook, $payload = null, $options = []) {

        $options = array_merge([
            'curl_command' => false,
        ], $options);

        $headers = [];
        $data    = null;
        $url = \DotEnv::resolveEnvsInString(trim($webhook['url']));
        $method = $webhook['method'] ?? 'POST';

        if (
            !filter_var($url , FILTER_VALIDATE_URL) ||
            !preg_match('#^(http|https)://#', $url)
        ) {
            return $this->stop(['error' => 'Invalid webhook url'], 412);
        }

        if (isset($webhook['body']['payload']) && $webhook['body']['payload']) {

            if ($webhook['body']['custom']) {
                $data = $webhook['body']['contents'] ?? [];
            } elseif ($payload) {
                $data = $payload;
            }

            if ($data && is_array($data)) {

                \DotEnv::resolveEnvsInArray($data);

                if ($method == 'GET') {
                    $data = http_build_query($data);
                } else {
                    $data = json_encode($data);
                }
            }
        }

        // add custom headers
        if (isset($webhook['headers']) && is_array($webhook['headers'])) {

            foreach ($webhook['headers'] as $h) {

                if (!isset($h['key'], $h['val'])) continue;
                if (!$h['key'] || !$h['val']) continue;

                $headers[] = implode(': ', [$h['key'], \DotEnv::resolveEnvsInString($h['val'])]);
            }
        }

        if ($data && $method == 'GET') {
            $url .= (str_contains($url, '?') ? '&':'?').$data;
        }

        $ch = curl_init($url);

        if (!str_starts_with($url, 'https://')) {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        }

        curl_setopt($ch, CURLOPT_TIMEOUT, 5);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        if ($data && $method != 'GET') {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
            $headers[] = 'Content-Type: application/json';
            $headers[] = 'Content-Length:'.strlen($data);
        }

        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        // Prepare the result array structure
        $result = [
            'success' => false,
            'status_code' => 0,
            'headers' => [],
            'content' => '',
            'duration_ms' => 0,
        ];

        $error = null;
        $response = curl_exec($ch);

        if ($response === false) {
            $errorCode = curl_errno($ch);
            $errorMessage = curl_error($ch);

            $error = [
                'code' => $errorCode,
                'message' => $errorMessage
            ];

            $result['error'] = $error;
        } else {

            $result['status_code'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $result['duration_ms'] = round(curl_getinfo($ch, CURLINFO_TOTAL_TIME) * 1000);

            // Parse headers and content
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $headerText = substr($response, 0, $headerSize);
            $result['content'] = substr($response, $headerSize);

            // Parse headers into an associative array
            $headers = [];
            foreach (explode("\r\n", $headerText) as $i => $line) {

                if (empty(trim($line))) continue;

                if ($i === 0) {
                    // First line contains the HTTP response code
                    $result['status_line'] = $line;
                    continue;
                }

                $parts = explode(':', $line, 2);

                if (count($parts) === 2) {
                    $key = trim($parts[0]);
                    $value = trim($parts[1]);

                    // Handle multiple headers with the same name
                    if (isset($headers[$key])) {
                        if (is_array($headers[$key])) {
                            $headers[$key][] = $value;
                        } else {
                            $headers[$key] = [$headers[$key], $value];
                        }
                    } else {
                        $headers[$key] = $value;
                    }
                }
            }

            $result['headers'] = $headers;

            // Set success flag based on HTTP status code
            $result['success'] = $result['status_code'] >= 200 && $result['status_code'] < 300;

            if ($result['status_code'] >= 400) {
                $error = [
                    'code' => $result['status_code'],
                    'message' => $result['content']
                ];
                $result['error'] = $error;
            }
        }

        if ($error) {
            $this->app->module('system')->log("Webhook failed: {$webhook['name']}", channel: 'webhooks', type: 'error', context: $error);
        }

        if ($options['curl_command']) {
            $result['curl_command'] = $this->app->helper('webhooks')->generateCurlCommand($webhook, $payload);
        }

        curl_close($ch);

        return $result;
    }
]);


// load admin related code
$this->on('app.admin.init', function() {
    include(__DIR__.'/admin.php');
});

$useWorker = $this->retrieve('webhooks/worker', false);

if ($useWorker) {

    $this->on('worker.handlers.collect', function($handlers) {

        $handlers['webhooks:trigger'] = function($data, $context) {

            $trigger = $data['trigger'] ?? [];

            try {

                $tasks = [];

                foreach ($trigger as $t) {
                    $tasks[] = function($app) use($t) {
                        $app->module('webhooks')->execute($t['webhook'], $t['payload']);
                    };
                }

                $this->helper('async')->batch($tasks);

            } catch (Throwable $e) {
                $context['error'] = $e->getMessage();
                return false;
            }

            return true;
        };
    });
}


// register events defined for webhooks
$this->on(['app.api.request', 'app.admin.request'], function($request) use($useWorker) {

    if (in_array($request->route, ['/app-event-stream', '/check-session', '/app.i18n.data.js', '/utils/csrf/login'])) {
        return;
    }

    $trigger = new ArrayObject([]);

    try {

        $webhooks = $this->helper('webhooks')->hooks();

        if (!count($webhooks)) {
            return;
        }

        foreach ($webhooks as $webhook) {

            if (!isset($webhook['events']) || !$webhook['enabled'] || !count($webhook['events'])) continue;

            $this->on($webhook['events'], function() use($webhook, $trigger) {
                $trigger[] = ['webhook' => $webhook, 'payload' => func_get_args()];
            });
        }

    } catch (Throwable $e) {
        return;
    }

    $this->on('after', function() use($trigger, $useWorker) {

        if (!count($trigger)) return;

        if ($useWorker) {
            $this->helper('worker')->push('webhooks:trigger', ['trigger' => $trigger->getArrayCopy()]);
            return;
        }

        $this->helper('async')->exec('

            $tasks = [];

            foreach ($trigger as $t) {
                $tasks[] = function($app) use($t) {
                    $app->module("webhooks")->execute($t["webhook"], $t["payload"]);
                };
            }

            Cockpit()->helper("async")->batch($tasks);
        ', ['trigger' => $trigger->getArrayCopy()]);
    });

}, -1000);
