<?php

declare(strict_types=1);

namespace App\Framework\Deployment\Webhook\Http;

use App\Framework\Attributes\Route;
use App\Framework\Deployment\Pipeline\Services\DeploymentPipelineService;
use App\Framework\Deployment\Pipeline\ValueObjects\DeploymentEnvironment;
use App\Framework\EventBus\EventBus;
use App\Framework\Http\JsonResult;
use App\Framework\Http\Method;
use App\Framework\Http\Request;
use App\Framework\Http\Status;
use App\Framework\Logging\Logger;

/**
 * Deployment Webhook Controller
 *
 * Receives webhooks from Gitea Actions after successful builds and triggers
 * the framework's deployment pipeline using the Process module.
 *
 * This is the missing integration layer that enables automatic deployment
 * on git push by connecting Gitea Actions to the existing deployment infrastructure.
 */
final readonly class DeploymentWebhookController
{
    public function __construct(
        private DeploymentPipelineService $pipelineService,
        private EventBus $eventBus,
        private Logger $logger
    ) {}

    /**
     * Handle deployment webhook from Gitea Actions
     *
     * Expected payload from Gitea Actions:
     * {
     *   "image_tag": "abc1234-1234567890",
     *   "commit_sha": "abc1234",
     *   "branch": "main",
     *   "environment": "production",
     *   "webhook_token": "secret_token"
     * }
     *
     * @route POST /api/deployment/webhook
     */
    #[Route(path: '/api/deployment/webhook', method: Method::POST)]
    public function handleWebhook(Request $request): JsonResult
    {
        $this->logger->info('[Deployment Webhook] Received webhook request', [
            'ip' => $request->server->getRemoteAddr(),
            'user_agent' => $request->server->getUserAgent()->toString(),
        ]);

        try {
            // Parse and validate webhook payload
            $payload = $this->parseAndValidatePayload($request);

            // Validate webhook token for security
            if (!$this->validateWebhookToken($payload['webhook_token'] ?? '')) {
                $this->logger->warning('[Deployment Webhook] Invalid webhook token', [
                    'ip' => $request->server->getRemoteAddr(),
                ]);

                return new JsonResult(
                    data: ['error' => 'Invalid webhook token'],
                    status: Status::FORBIDDEN
                );
            }

            // Parse environment
            $environment = $this->parseEnvironment($payload['environment'] ?? 'production');

            $this->logger->info('[Deployment Webhook] Starting deployment pipeline', [
                'environment' => $environment->value,
                'image_tag' => $payload['image_tag'],
                'commit_sha' => $payload['commit_sha'],
                'branch' => $payload['branch'],
            ]);

            // Trigger existing deployment pipeline service
            // This uses the framework's Process module via AnsibleDeployStage
            $result = $this->pipelineService->execute($environment);

            if ($result->isSuccess()) {
                $this->logger->info('[Deployment Webhook] Pipeline executed successfully', [
                    'pipeline_id' => $result->pipelineId->value,
                    'environment' => $environment->value,
                    'duration' => $result->totalDuration->toMilliseconds() . 'ms',
                ]);

                return new JsonResult(
                    data: [
                        'status' => 'success',
                        'message' => 'Deployment pipeline started successfully',
                        'pipeline_id' => $result->pipelineId->value,
                        'environment' => $environment->getDisplayName(),
                        'duration' => $result->totalDuration->toMilliseconds(),
                        'stages_completed' => $result->getCompletedStagesCount(),
                        // SSE endpoint for real-time status tracking
                        'status_stream_url' => "/api/deployment/stream/{$result->pipelineId->value}",
                    ],
                    status: Status::OK
                );
            }

            // Pipeline failed or was rolled back
            $this->logger->error('[Deployment Webhook] Pipeline failed', [
                'pipeline_id' => $result->pipelineId->value,
                'status' => $result->status->value,
                'error' => $result->error ?? 'Unknown error',
            ]);

            return new JsonResult(
                data: [
                    'status' => 'failed',
                    'message' => 'Deployment pipeline failed',
                    'pipeline_id' => $result->pipelineId->value,
                    'pipeline_status' => $result->status->value,
                    'error' => $result->error ?? 'Unknown error',
                    'failed_stage' => $result->getFailedStage()?->stage->value,
                    'was_rolled_back' => $result->wasRolledBack(),
                ],
                status: Status::INTERNAL_SERVER_ERROR
            );
        } catch (\InvalidArgumentException $e) {
            $this->logger->warning('[Deployment Webhook] Invalid request payload', [
                'error' => $e->getMessage(),
            ]);

            return new JsonResult(
                data: ['error' => $e->getMessage()],
                status: Status::BAD_REQUEST
            );
        } catch (\Throwable $e) {
            $this->logger->error('[Deployment Webhook] Unexpected error', [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            return new JsonResult(
                data: [
                    'error' => 'Internal server error during deployment',
                    'message' => $e->getMessage(),
                ],
                status: Status::INTERNAL_SERVER_ERROR
            );
        }
    }

    /**
     * Parse and validate webhook payload
     *
     * @throws \InvalidArgumentException if payload is invalid
     */
    private function parseAndValidatePayload(Request $request): array
    {
        $body = $request->parsedBody;

        if ($body === null) {
            throw new \InvalidArgumentException('Request body is required');
        }

        $payload = $body->toArray();

        // Validate required fields
        $requiredFields = ['image_tag', 'commit_sha', 'branch'];
        foreach ($requiredFields as $field) {
            if (empty($payload[$field])) {
                throw new \InvalidArgumentException("Missing required field: {$field}");
            }
        }

        return $payload;
    }

    /**
     * Validate webhook token for security
     *
     * Compares provided token against configured secret token to prevent
     * unauthorized deployment triggers.
     */
    private function validateWebhookToken(string $token): bool
    {
        // Get webhook secret from environment
        $expectedToken = $_ENV['DEPLOYMENT_WEBHOOK_TOKEN'] ?? '';

        if (empty($expectedToken)) {
            $this->logger->warning('[Deployment Webhook] DEPLOYMENT_WEBHOOK_TOKEN not configured');
            return false;
        }

        // Constant-time comparison to prevent timing attacks
        return hash_equals($expectedToken, $token);
    }

    /**
     * Parse deployment environment from string
     *
     * @throws \InvalidArgumentException if environment is invalid
     */
    private function parseEnvironment(string $environment): DeploymentEnvironment
    {
        return match (strtolower($environment)) {
            'development', 'dev' => DeploymentEnvironment::DEVELOPMENT,
            'staging', 'stage' => DeploymentEnvironment::STAGING,
            'production', 'prod' => DeploymentEnvironment::PRODUCTION,
            default => throw new \InvalidArgumentException(
                "Invalid environment: {$environment}. Must be one of: development, staging, production"
            ),
        };
    }
}
