<?php

namespace ZenoChatbot\Framework\Foundation\Handler;

use ZenoChatbot\Framework\Foundation\Application;

class RestHandler
{
    protected Application $app;

    /**
     * Base namespace for short controller class names.
     * Single source of truth for controller namespacing.
     */
    /**
     * Wrap a short class name with the controller base namespace.
     * If an FQCN is provided, it is returned unchanged (without leading backslash).
     */
    public function wrapWithNameSpace(string $class, ?string $baseNamespace = null): string
    {
        $raw   = $class;
        $class = ltrim($class, '\\');

        // Compute base and vendor root prefixes from existing config
        $base = $this->getControllerBaseNamespace($baseNamespace);
        $root = $this->getProjectRootNamespacePrefix($base);

        // Explicit FQCN (leading backslash in input) -> return unchanged (sans leading slash)
        if (is_string($raw) && strlen($raw) > 0 && $raw[0] === '\\') {
            return $class;
        }

        // Already within the configured base namespace
        if ($base !== '' && (strpos($class, $base . '\\') === 0 || $class === $base)) {
            return $class;
        }

        // Already within the project vendor root namespace
        if ($root !== '' && strpos($class, $root) === 0) {
            return $class;
        }

        // Treat nested segments (e.g., "Api\\V1\\Foo") as relative to base
        if ($base !== '') {
            return $base . '\\' . $class;
        }

        // Fallback: return as-is (no base configured)
        return $class;
    }

    /**
     * Get the controller base namespace from config or explicit argument.
     */
    private function getControllerBaseNamespace(?string $baseNamespace = null): string
    {
        $configuredBase = $baseNamespace ?: $this->app->config('app.rest.controller_namespace');
        $base = is_string($configuredBase) ? $configuredBase : '';
        return rtrim((string) $base, '\\');
    }

    /**
     * Derive the project vendor root (first segment) as a namespace prefix.
     * Examples: "ZenoChatbot\\Http\\Controller" => "ZenoChatbot\\"
     */
    private function getProjectRootNamespacePrefix(?string $base = null): string
    {
        $base = $base ?? $this->getControllerBaseNamespace();
        if ($base === '') {
            return '';
        }
        $parts = explode('\\', $base);
        $vendor = $parts[0] ?? '';
        return $vendor !== '' ? $vendor . '\\' : '';
    }

    public function __construct(Application $app)
    {
        $this->app = $app;
    }

    /**
     * Parse a REST handler definition into a callable.
     * Supports:
     * - callable (closures, [ClassName, 'method'], [instance, 'method'])
     * - "Controller@method" (optionally FQCN; if no namespace, defaults to ZenoChatbot\\Http\\Controller)
     *
     * Everything is resolved and invoked through the container via makeWith($callable, $params).
     *
     * @param mixed $handler
     * @return callable
     */
    public function parseHandler($handler)
    {
        // Already a callable (closure or [obj,'method'] or invokable)
        if (is_callable($handler) && !(is_string($handler))) {
            return function ($request) use ($handler) {
                return $this->invokeWithRequest($handler, $request);
            };
        }

        // "Controller@method" (either short class or FQCN)
        if (is_string($handler) && strpos($handler, '@') !== false) {
            [$classPart, $method] = explode('@', $handler, 2);
            $controllerClass = $this->wrapWithNameSpace($classPart);

            return function ($request) use ($controllerClass, $method) {
                return $this->resolveController($controllerClass, $method, $request);
            };
        }

        // Fallbacks
        // 1) Plain string class name -> treat as invokable controller
        if (is_string($handler)) {
            return function ($request) use ($handler) {
                $fqcn = $this->wrapWithNameSpace($handler);
                return $this->invokeWithRequest([$fqcn, '__invoke'], $request);
            };
        }

        // 2) [Class, 'method']
        if (is_array($handler) && isset($handler[0], $handler[1])) {
            $normalized = is_string($handler[0]) ? [$this->wrapWithNameSpace($handler[0]), $handler[1]] : $handler;
            return function ($request) use ($normalized) {
                return $this->invokeWithRequest($normalized, $request);
            };
        }

        throw new \InvalidArgumentException("Invalid REST handler: " . print_r($handler, true));
    }


    /**
     * Resolve controller+method and return a WP_REST_Response using container->makeWith
     *
     * @param string $controllerClass Class name (short or FQCN)
     * @param string $method
     * @param \WP_REST_Request $request
     * @return \WP_REST_Response|\WP_Error
     */
    public function resolveController($controllerClass, $method, $request)
    {
        try {
            $controllerClass = $this->wrapWithNameSpace($controllerClass);
            return $this->invokeWithRequest([$controllerClass, $method], $request);
        } catch (\Exception $e) {
            return new \WP_REST_Response([
                'success' => false,
                'message' => $e->getMessage(),
                'error'   => $e->getMessage(),
                'code'    => $e->getCode(),
            ], $e->getCode() ?: 500);
        }
    }


    /**
     * Call a handler via the container and format the REST response.
     * Supports callable, [ClassName, method], and "Controller@method" strings.
     */
    public function handle($handler, $request = null)
    {
        try {
            // Reuse the single parsing path to avoid duplicated logic
            $callable = $this->parseHandler($handler);
            return $callable($request);
        } catch (\Exception $e) {
            return new \WP_REST_Response([
                'success' => false,
                'message' => $e->getMessage(),
                'error'   => $e->getMessage(),
                'code'    => $e->getCode(),
            ], $e->getCode() ?: 500);
        }
    }

    /**
     * Invoke a target callable via container with WP request bound and params injected.
     *
     * @param callable|array|string $target
     * @param mixed $request
     * @return \WP_REST_Response
     * @throws \Exception
     */
    private function invokeWithRequest($target, $request)
    {
        $params = [];

        if ($request instanceof \WP_REST_Request) {
            $this->bindRequestToContainer($request);
            $params = $request->get_params() ?: [];
        }

        $result = $this->app->makeWith($target, $params);
        return rest_ensure_response($result);
    }

    /**
     * Bind WP_REST_Request and framework Request into the container for this cycle.
     */
    private function bindRequestToContainer($request)
    {
        if ($request instanceof \WP_REST_Request) {
            $this->app->instance(\WP_REST_Request::class, $request);
            $this->app->instance('wp.rest.request', $request);
            $customRequest = new \ZenoChatbot\Framework\Http\Request($request);
            $this->app->instance(\ZenoChatbot\Framework\Http\Request::class, $customRequest);
            $this->app->instance('request', $customRequest);
        }
    }

    public static function create(Application $app)
    {
        return new static($app);
    }
}
