<?php
/**
 * GeneratePostmanCollection Command Class
 *
 * @version     1.0.0
 * @package     bplan-base/globals
 * @subpackage  Console\Commands
 * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
 * @copyright   Copyright (C) 2025, 2026 bplan-solutions GmbH & Co. KG <https://www.bplan-solutions.de/>
 * /Δ\
 */

namespace BplanBase\Globals\Console\Commands;


use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;


/**
 * Generates a Postman Collection v2.1 JSON file from the application's routes.
 *
 * Filters routes by URI prefix (default: api/data and api/terminal), groups
 * them by resource, and writes the collection to storage/app/postman-collection.json.
 *
 * @version     1.0.0 / 2026-04-28
 * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
 */
class GeneratePostmanCollection extends Command
{


/* +++ TRAITS +++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */


/* +++ CLASS CONSTANTS +++ ++++++++++++++++++++++++++++++++++++++++++++++++++ */


/* +++ OBJECT MEMBERS +++ +++++++++++++++++++++++++++++++++++++++++++++++++++ */


    /**
     * @var     string
     */
    protected $description = 'Generate a Postman Collection JSON from the application routes';

    /**
     * @var     string
     */
    protected $signature = 'globals:generate-postman-collection
                            {--path= : Filter routes by URI prefix (e.g. api/data). Comma-separated for multiple prefixes. Defaults to api/data,api/terminal.}';


/* +++ CLASS MEMBERS +++ ++++++++++++++++++++++++++++++++++++++++++++++++++++ */


/* +++ OBJECT METHODS +++ +++++++++++++++++++++++++++++++++++++++++++++++++++ */


    /**
     * @param       array $routes
     * @param       array $headers
     *
     * @return      array
     *
     * @version     1.0.0 / 2026-04-28
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    protected function _buildResourceItems(array $routes, array $headers): array
    {
        $crud         = [];
        $related      = [];
        $relationship = [];

        foreach ($routes as $route) {
            $uri    = $route['uri'];
            $method = strtok($route['method'], '|');
            $name   = $route['name'] ?? '';

            $segments = explode('/', $uri);
            $depth    = count($segments);

            $hasId           = str_contains($uri, '{');
            $isRelationships = str_contains($uri, '/relationships/');
            $isRelated       = $hasId && ($isRelationships === false) && $depth > 3 && !in_array($method, ['PATCH', 'DELETE', 'POST']);

            $nameParts = explode('.', $name);
            $action    = end($nameParts);

            $hasBody = in_array($method, ['POST', 'PATCH', 'PUT']);

            $label = match (true) {
                str_contains($uri, '/relationships/') => 'Relationship: ' . end($segments),
                $action === 'index'                   => 'List',
                $action === 'store'                   => 'Create',
                $action === 'show'                    => 'Show',
                $action === 'update'                  => 'Update',
                $action === 'destroy'                 => 'Delete',
                $action === 'showRelated'             => 'Related: ' . end($segments),
                $action === 'showRelationship'        => 'Relationship: ' . end($segments),
                default                               => $action . ($method !== 'GET' ? ' [' . $method . ']' : ''),
            };

            $item = $this->_makeRequest($label, $method, $uri, $headers, $hasBody);

            if ($action === 'showRelationship' || str_contains($uri, '/relationships/')) {
                $relationship[] = $item;
            } elseif ($action === 'showRelated' && ($isRelated === true)) {
                $related[] = $item;
            } else {
                $crud[] = $item;
            }
        }

        $items = $crud;

        if ($related !== []) {
            $items[] = ['name' => 'Related Resources', 'item' => $related];
        }

        if ($relationship !== []) {
            $items[] = ['name' => 'Relationships', 'item' => $relationship];
        }

        return $items;

    } // _buildResourceItems()


    /**
     * @param       string $name
     * @param       string $uri
     * @param       array  $headers
     *
     * @return      array
     *
     * @version     1.0.0 / 2026-04-28
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    protected function _buildJsonRpcItem(string $name, string $uri, array $headers): array
    {
        $path = $this->_uriToPath($uri);

        return [
            'name'    => $name,
            'request' => [
                'method' => 'POST',
                'header' => $headers,
                'url'    => [
                    'raw'  => '{{baseUrl}}/' . implode('/', $path),
                    'host' => ['{{baseUrl}}'],
                    'path' => $path,
                ],
                'body' => [
                    'mode' => 'raw',
                    'raw'  => "{\n  \"jsonrpc\": \"2.0\",\n  \"method\": \"\",\n  \"params\": {},\n  \"id\": 1\n}",
                    'options' => ['raw' => ['language' => 'json']],
                ],
            ],
        ];

    } // _buildJsonRpcItem()


    /**
     * @param       array  $routes
     * @param       string $prefix     e.g. 'data' or 'terminal'
     * @param       array  $commonHeaders
     * @param       array  $jsonRpcHeaders
     *
     * @return      array
     *
     * @version     1.0.0 / 2026-04-28
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    protected function _buildPrefixFolder(array $routes, string $prefix, array $commonHeaders, array $jsonRpcHeaders): array
    {
        $label  = ucfirst($prefix) . ' API';
        $folder = ['name' => $label, 'item' => []];

        $grouped      = [];
        $jsonRpcRoutes = [];

        foreach ($routes as $route) {
            $uri      = $route['uri'];
            $rest     = substr($uri, strlen('api/' . $prefix . '/'));
            $resource = explode('/', $rest)[0];

            if (str_contains($route['action'] ?? '', 'JsonRpcController')) {
                $jsonRpcRoutes[] = $route;
            } else {
                $grouped[$resource][] = $route;
            }
        }

        ksort($grouped);

        foreach ($grouped as $resource => $resourceRoutes) {
            $folder['item'][] = [
                'name' => $resource,
                'item' => $this->_buildResourceItems($resourceRoutes, $commonHeaders),
            ];
        }

        if ($jsonRpcRoutes !== []) {
            $rpcFolder = ['name' => 'JSON-RPC', 'item' => []];

            foreach ($jsonRpcRoutes as $r) {
                $rpcFolder['item'][] = $this->_buildJsonRpcItem(
                    $r['name'] ?? $r['uri'],
                    $r['uri'],
                    $jsonRpcHeaders
                );
            }

            $folder['item'][] = $rpcFolder;
        }

        return $folder;

    } // _buildPrefixFolder()


    /**
     * @param       string $name
     * @param       string $method
     * @param       string $uri
     * @param       array  $headers
     * @param       bool   $hasBody
     *
     * @return      array
     *
     * @version     1.0.0 / 2026-04-28
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    protected function _makeRequest(string $name, string $method, string $uri, array $headers, bool $hasBody = false): array
    {
        $path = $this->_uriToPath($uri);

        $request = [
            'name'    => $name,
            'request' => [
                'method' => $method,
                'header' => $headers,
                'url'    => [
                    'raw'  => '{{baseUrl}}/' . implode('/', $path),
                    'host' => ['{{baseUrl}}'],
                    'path' => $path,
                ],
            ],
        ];

        if ($hasBody === true) {
            $request['request']['body'] = [
                'mode' => 'raw',
                'raw'  => "{\n  \"data\": {\n    \"type\": \"\",\n    \"attributes\": {}\n  }\n}",
                'options' => ['raw' => ['language' => 'json']],
            ];
        }

        return $request;

    } // _makeRequest()


    /**
     * @param       string $uri
     *
     * @return      array
     *
     * @version     1.0.0 / 2026-04-28
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    protected function _uriToPath(string $uri): array
    {
        return array_map(
            fn ($s) => preg_replace('/^\{(.+)\}$/', '{{$1}}', $s),
            explode('/', $uri)
        );

    } // _uriToPath()


    /**
     * Execute the console command.
     *
     * @version     1.0.0 / 2026-04-28
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public function handle(): int
    {
        $pathOption = $this->option('path') ?? 'api/data,api/terminal';
        $prefixes   = array_map('trim', explode(',', $pathOption));

        $this->info('Fetching routes …');

        Artisan::call('route:list', ['--json' => true]);
        $routes = json_decode(Artisan::output(), true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            $this->error('Failed to parse route list JSON: ' . json_last_error_msg());

            return self::FAILURE;
        }

        $commonHeaders = [
            ['key' => 'Accept',        'value' => 'application/vnd.api+json'],
            ['key' => 'Content-Type',  'value' => 'application/vnd.api+json'],
            ['key' => 'Authorization', 'value' => 'Bearer {{token}}'],
        ];

        $jsonRpcHeaders = [
            ['key' => 'Accept',        'value' => 'application/json'],
            ['key' => 'Content-Type',  'value' => 'application/json'],
            ['key' => 'Authorization', 'value' => 'Bearer {{token}}'],
        ];

        $collection = [
            'info' => [
                'name'   => config('app.name', 'Laravel') . ' API [' . config('app.url', 'http://localhost') . ']',
                'schema' => 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json',
            ],
            'variable' => [
                ['key' => 'baseUrl',    'value' => config('app.url', 'http://localhost'), 'type' => 'string'],
                ['key' => 'token',      'value' => '',                                    'type' => 'string'],
                ['key' => 'tenantUuid', 'value' => env('GLOBAL_TENANT', ''),              'type' => 'string'],
            ],
            'event' => [
                [
                    'listen' => 'prerequest',
                    'script' => [
                        'type' => 'text/javascript',
                        'exec' => [
                            'pm.request.headers.add({ key: "Api-Tenant-UUID", value: pm.collectionVariables.get("tenantUuid") });',
                        ],
                    ],
                ],
            ],
            'item' => [],
        ];

        foreach ($prefixes as $prefix) {
            // Normalize: strip leading slash if provided
            $prefix  = ltrim($prefix, '/');
            $matched = array_filter($routes, fn ($r) => str_starts_with($r['uri'], $prefix . '/') || $r['uri'] === $prefix);

            if ($matched === []) {
                $this->warn('No routes found for prefix: ' . $prefix);
                continue;
            }

            // Derive short prefix name (last segment of prefix, e.g. 'api/data' → 'data')
            $segments      = explode('/', $prefix);
            $prefixLabel   = end($segments);

            $collection['item'][] = $this->_buildPrefixFolder(array_values($matched), $prefixLabel, $commonHeaders, $jsonRpcHeaders);
        }

        $outputPath = storage_path('app/public/postman-collection.json');

        file_put_contents(
            $outputPath,
            json_encode($collection, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
        );

        $this->info('Collection written to: ' . $outputPath);

        return self::SUCCESS;

    } // handle()


/* +++ CLASS METHODS +++ ++++++++++++++++++++++++++++++++++++++++++++++++++++ */


} // class GeneratePostmanCollection extends Command {}
