<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents\Controllers;

use App\Framework\Attributes\Route;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Http\Status;
use App\Framework\Http\UploadedFile;
use App\Framework\LiveComponents\Batch\BatchProcessor;
use App\Framework\LiveComponents\Batch\BatchRequest;
use App\Framework\LiveComponents\ComponentRegistry;
use App\Framework\LiveComponents\Contracts\LifecycleAware;
use App\Framework\LiveComponents\Contracts\SupportsFileUpload;
use App\Framework\LiveComponents\LiveComponentHandler;
use App\Framework\LiveComponents\Rendering\FragmentRenderer;
use App\Framework\LiveComponents\ValueObjects\ActionParameters;
use App\Framework\LiveComponents\ValueObjects\ComponentAction;
use App\Framework\LiveComponents\ValueObjects\ComponentData;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\Router\Result\JsonResult;

final readonly class LiveComponentController
{
    public function __construct(
        private ComponentRegistry $componentRegistry,
        private LiveComponentHandler $handler,
        private FragmentRenderer $fragmentRenderer,
        private BatchProcessor $batchProcessor
    ) {
    }

    #[Route('/live-component/{id}', method: Method::POST)]
    public function handleAction(string $id, HttpRequest $request): JsonResult
    {
        $action = ComponentAction::fromRequest($request, $id);

        // Get state from request (as array, will be passed to component constructor)
        $stateArray = $request->parsedBody?->get('state', []);
        $initialData = is_array($stateArray) ? $stateArray : null;

        // Get fragments parameter if present (for partial rendering)
        $fragmentNames = $request->parsedBody?->get('fragments', []);
        $fragmentNames = is_array($fragmentNames) ? $fragmentNames : [];

        // Resolve component with current state (registry will convert array to State VO)
        $component = $this->componentRegistry->resolve(
            $action->componentId,
            $initialData
        );

        // Handle action using LiveComponentHandler (composition)
        // Returns ComponentUpdate with empty HTML
        $update = $this->handler->handle(
            $component,
            $action->method,
            $action->params
        );

        // Create new component instance with updated data for rendering
        $updatedComponent = $this->componentRegistry->resolve(
            $action->componentId,
            $update->state->data // Array will be converted to State VO by registry
        );

        // Render fragments or full HTML based on request
        if (! empty($fragmentNames)) {
            // Partial rendering: extract requested fragments
            $fragmentCollection = $this->fragmentRenderer->renderFragments(
                $updatedComponent,
                $fragmentNames
            );

            // If fragments found, return them
            if (! $fragmentCollection->isEmpty()) {
                return new JsonResult([
                    'fragments' => $fragmentCollection->toAssociativeArray(),
                    'state' => $update->state->toArray(),
                    'events' => $update->events,
                ]);
            }

            // Fallback to full render if fragments not found
        }

        // Full rendering (default or fallback)
        $html = $this->componentRegistry->render($updatedComponent);

        // Return ComponentUpdate with rendered HTML
        return new JsonResult([
            'html' => $html,
            'state' => $update->state->toArray(),  // ✅ Prevent double-encoding
            'events' => $update->events,
        ]);
    }

    #[Route('/live-component/{id}/upload', method: Method::POST)]
    public function handleUpload(string $id, HttpRequest $request): JsonResult
    {
        // Convert string ID to ComponentId
        $componentId = ComponentId::fromString($id);

        // Get state from request (as array)
        $stateArray = $request->parsedBody?->get('state', []);
        $initialData = is_array($stateArray) ? $stateArray : null;

        $params = $request->parsedBody?->get('params', []);

        // Resolve component (registry will convert array to State VO)
        $component = $this->componentRegistry->resolve(
            $componentId,
            $initialData
        );

        // Check if component supports file uploads
        if (! $component instanceof SupportsFileUpload) {
            return new JsonResult([
                'success' => false,
                'error' => 'Component does not support file uploads',
            ], Status::BAD_REQUEST);
        }

        // Get uploaded file from request
        $uploadedFiles = $request->files?->all() ?? [];

        if (empty($uploadedFiles)) {
            return new JsonResult([
                'success' => false,
                'error' => 'No file uploaded',
            ], Status::BAD_REQUEST);
        }

        // Get first uploaded file (support for multiple files can be added later)
        $file = reset($uploadedFiles);

        if (! $file instanceof UploadedFile) {
            return new JsonResult([
                'success' => false,
                'error' => 'Invalid file upload',
            ], Status::BAD_REQUEST);
        }

        // Validate file
        $validationErrors = $component->validateUpload($file);

        if (! empty($validationErrors)) {
            return new JsonResult([
                'success' => false,
                'errors' => $validationErrors,
            ], Status::UNPROCESSABLE_ENTITY);
        }

        try {
            // Handle upload through handler (like regular actions)
            $update = $this->handler->handleUpload(
                $component,
                $file,
                ActionParameters::fromArray(is_array($params) ? $params : [])
            );

            // Create new component instance with updated data for rendering
            $updatedComponent = $this->componentRegistry->resolve(
                $componentId,
                $update->state->data // Array will be converted to State VO
            );

            // Render HTML using ComponentRegistry
            $html = $this->componentRegistry->render($updatedComponent);

            // Return success response
            return new JsonResult([
                'success' => true,
                'html' => $html,
                'state' => $update->state->toArray(),  // ✅ Prevent double-encoding
                'events' => $update->events,
                'file' => [
                    'name' => $file->name,
                    'size' => $file->size,
                    'type' => $file->type,
                ],
            ]);
        } catch (\Exception $e) {
            return new JsonResult([
                'success' => false,
                'error' => $e->getMessage(),
            ], Status::INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * Handle lazy loading of component
     *
     * Renders component HTML on-demand when it enters viewport.
     * Used by LazyComponentLoader for performance optimization.
     *
     * @param string $id Component ID
     * @param HttpRequest $request HTTP request
     * @return JsonResult JSON response with component HTML and state
     */
    #[Route('/live-component/{id}/lazy-load', method: Method::GET)]
    public function handleLazyLoad(string $id, HttpRequest $request): JsonResult
    {
        try {
            $componentId = ComponentId::fromString($id);

            // Resolve component with initial state (null = component will use its defaults)
            $component = $this->componentRegistry->resolve(
                $componentId,
                initialData: null
            );

            // Render component HTML with wrapper
            $html = $this->componentRegistry->renderWithWrapper($component);

            // Get component state
            $componentState = $component->state;

            // Get CSRF token for component (each component has its own token)
            $csrfToken = $this->componentRegistry->generateCsrfToken($componentId);

            return new JsonResult([
                'success' => true,
                'html' => $html,
                'state' => $componentState->toArray(),
                'csrf_token' => $csrfToken,
                'component_id' => $componentId->toString(),
            ]);

        } catch (\Exception $e) {
            return new JsonResult([
                'success' => false,
                'error' => $e->getMessage(),
                'error_code' => 'LAZY_LOAD_FAILED',
            ], Status::INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * Handle component destruction lifecycle hook
     *
     * Called by client-side JavaScript when component element is removed from DOM.
     * Only calls onDestroy() if component implements LifecycleAware interface.
     */
    #[Route('/live-component/{id}/destroy', method: Method::POST)]
    public function handleDestroy(string $id, HttpRequest $request): JsonResult
    {
        try {
            $componentId = ComponentId::fromString($id);

            // Get state from request
            $stateArray = $request->parsedBody?->get('state', []);
            $initialData = is_array($stateArray) ? $stateArray : null;

            // Resolve component with current state
            $component = $this->componentRegistry->resolve(
                $componentId,
                $initialData
            );

            // Only call onDestroy() if component implements LifecycleAware
            if ($component instanceof LifecycleAware) {
                try {
                    $component->onDestroy();
                } catch (\Throwable $e) {
                    // Log but don't fail - component is being destroyed anyway
                    error_log("Lifecycle hook onDestroy() failed for " . get_class($component) . ": " . $e->getMessage());
                }
            }

            return new JsonResult([
                'success' => true,
                'message' => 'Component destroyed',
            ]);

        } catch (\Exception $e) {
            // Even if destroy fails, return success - component is gone from client
            return new JsonResult([
                'success' => true,
                'message' => 'Component destroyed (with errors)',
                'error' => $e->getMessage(),
            ]);
        }
    }

    /**
     * Handle batch request - multiple component operations in one request
     *
     * Request Format:
     * {
     *   "operations": [
     *     {
     *       "componentId": "counter:demo",
     *       "method": "increment",
     *       "params": {"amount": 5},
     *       "fragments": ["counter-display"],
     *       "operationId": "op-1"
     *     },
     *     {
     *       "componentId": "user-stats:123",
     *       "method": "refresh",
     *       "operationId": "op-2"
     *     }
     *   ]
     * }
     *
     * Response Format:
     * {
     *   "results": [
     *     {
     *       "success": true,
     *       "operationId": "op-1",
     *       "fragments": {"counter-display": "<span>5</span>"},
     *       "state": {"count": 5},
     *       "events": []
     *     },
     *     {
     *       "success": false,
     *       "operationId": "op-2",
     *       "error": "Component not found",
     *       "errorCode": "COMPONENT_NOT_FOUND"
     *     }
     *   ],
     *   "total_operations": 2,
     *   "success_count": 1,
     *   "failure_count": 1
     * }
     *
     * Features:
     * - Executes operations sequentially with error isolation
     * - Per-operation error handling (one failure doesn't stop batch)
     * - Fragment support for partial updates
     * - 60-80% fewer HTTP requests for multi-component updates
     */
    #[Route('/live-component/batch', method: Method::POST)]
    public function handleBatch(HttpRequest $request): JsonResult
    {
        try {
            // Parse batch request
            $requestData = $request->parsedBody?->toArray() ?? [];
            $batchRequest = BatchRequest::fromArray($requestData);

            // Process batch
            $batchResponse = $this->batchProcessor->process($batchRequest);

            // Return batch response
            return new JsonResult($batchResponse->toArray());

        } catch (\InvalidArgumentException $e) {
            // Invalid batch request format
            return new JsonResult([
                'error' => $e->getMessage(),
                'error_code' => 'INVALID_BATCH_REQUEST',
            ], Status::BAD_REQUEST);

        } catch (\Throwable $e) {
            // Unexpected error
            return new JsonResult([
                'error' => 'Batch processing failed: ' . $e->getMessage(),
                'error_code' => 'BATCH_PROCESSING_FAILED',
            ], Status::INTERNAL_SERVER_ERROR);
        }
    }
}
