<?php

namespace App\Services\Dokploy;

use App\Models\InfrastructureActionLog;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\Client\Factory as HttpFactory;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class DokployClient
{
    public function __construct(private readonly HttpFactory $http) {}

    public function applicationStatus(string $applicationId): array
    {
        return $this->cached($this->applicationCacheKey($applicationId), function () use ($applicationId) {
            $application = $this->get('/application.one', [
                'applicationId' => $applicationId,
            ]);

            $appName = Arr::get($application, 'appName') ?? Arr::get($application, 'name');
            $monitoring = [];

            if ($appName) {
                $monitoring = $this->optionalGet('/application.readAppMonitoring', [
                    'appName' => $appName,
                ]);
            }

            return [
                'application' => $application,
                'monitoring' => $monitoring,
                'appName' => $appName,
            ];
        }, 30);
    }

    public function recentDeployments(string $applicationId, int $limit = 5): array
    {
        return $this->cached($this->deploymentCacheKey($applicationId), function () use ($applicationId, $limit) {
            $deployments = $this->get('/deployment.all', [
                'applicationId' => $applicationId,
            ]);

            if (! is_array($deployments)) {
                return [];
            }

            return array_slice($deployments, 0, $limit);
        }, 60);
    }

    public function reloadApplication(string $applicationId, ?Authenticatable $actor = null): array
    {
        $status = $this->applicationStatus($applicationId);
        $appName = Arr::get($status, 'appName');

        if (! $appName) {
            throw new \RuntimeException('Dokploy application name is required to reload the service.');
        }

        $response = $this->dispatchAction(
            $applicationId,
            'reload',
            [
                'applicationId' => $applicationId,
                'appName' => $appName,
            ],
            fn (array $payload) => $this->post('/application.reload', $payload),
            $actor,
        );

        $this->forgetApplicationCaches($applicationId);

        return $response;
    }

    public function redeployApplication(string $applicationId, ?Authenticatable $actor = null): array
    {
        $response = $this->dispatchAction(
            $applicationId,
            'redeploy',
            [
                'applicationId' => $applicationId,
            ],
            fn (array $payload) => $this->post('/application.redeploy', $payload),
            $actor,
        );

        $this->forgetApplicationCaches($applicationId);

        return $response;
    }

    public function composeStatus(string $composeId): array
    {
        return $this->cached($this->composeCacheKey($composeId), function () use ($composeId) {
            $compose = $this->get('/compose.one', [
                'composeId' => $composeId,
            ]);

            $services = $this->optionalGet('/compose.loadServices', [
                'composeId' => $composeId,
                'type' => 'cache',
            ]);

            return [
                'compose' => $compose,
                'services' => $services,
            ];
        }, 30);
    }

    public function composeDeployments(string $composeId, int $limit = 5): array
    {
        return $this->cached($this->composeDeploymentsCacheKey($composeId), function () use ($composeId, $limit) {
            $deployments = $this->get('/deployment.allByCompose', [
                'composeId' => $composeId,
            ]);

            if (! is_array($deployments)) {
                return [];
            }

            return array_slice($deployments, 0, $limit);
        }, 60);
    }

    public function redeployCompose(string $composeId, ?Authenticatable $actor = null): array
    {
        $response = $this->dispatchAction(
            $composeId,
            'compose.redeploy',
            [
                'composeId' => $composeId,
            ],
            fn (array $payload) => $this->post('/compose.redeploy', $payload),
            $actor,
        );

        $this->forgetComposeCaches($composeId);

        return $response;
    }

    public function deployCompose(string $composeId, ?Authenticatable $actor = null): array
    {
        $response = $this->dispatchAction(
            $composeId,
            'compose.deploy',
            [
                'composeId' => $composeId,
            ],
            fn (array $payload) => $this->post('/compose.deploy', $payload),
            $actor,
        );

        $this->forgetComposeCaches($composeId);

        return $response;
    }

    public function stopCompose(string $composeId, ?Authenticatable $actor = null): array
    {
        $response = $this->dispatchAction(
            $composeId,
            'compose.stop',
            [
                'composeId' => $composeId,
            ],
            fn (array $payload) => $this->post('/compose.stop', $payload),
            $actor,
        );

        $this->forgetComposeCaches($composeId);

        return $response;
    }

    protected function cached(string $key, callable $callback, int $seconds): mixed
    {
        return Cache::remember($key, now()->addSeconds($seconds), $callback);
    }

    protected function optionalGet(string $path, array $query = []): array
    {
        try {
            return $this->get($path, $query);
        } catch (\Throwable $exception) {
            Log::warning('[Dokploy] Optional GET failed', [
                'path' => $path,
                'message' => $exception->getMessage(),
            ]);

            return [];
        }
    }

    protected function get(string $path, array $query = []): array
    {
        $response = $this->request()->get($this->normalizePath($path), $query);

        if ($response->failed()) {
            $this->logFailure('GET', $path, $response);
            throw new RequestException($response);
        }

        return $response->json() ?? [];
    }

    protected function post(string $path, array $payload = []): Response
    {
        $response = $this->request()->post($this->normalizePath($path), $payload);

        if ($response->failed()) {
            $this->logFailure('POST', $path, $response);
            throw new RequestException($response);
        }

        return $response;
    }

    protected function request(): PendingRequest
    {
        $baseUrl = config('dokploy.api.base_url');
        $token = config('dokploy.api.token');
        $timeout = config('dokploy.api.timeout', 10);

        if (! $baseUrl || ! $token) {
            throw new \RuntimeException('Dokploy API is not configured.');
        }

        return $this->http
            ->baseUrl($baseUrl)
            ->timeout($timeout)
            ->acceptJson()
            ->withHeaders([
                'x-api-key' => $token,
            ]);
    }

    protected function normalizePath(string $path): string
    {
        return '/'.ltrim($path, '/');
    }

    protected function logFailure(string $method, string $path, Response $response): void
    {
        Log::error('[Dokploy] API request failed', [
            'method' => $method,
            'path' => $path,
            'status' => $response->status(),
            'body' => $response->body(),
        ]);
    }

    protected function dispatchAction(
        string $targetId,
        string $action,
        array $payload,
        callable $callback,
        ?Authenticatable $actor = null,
    ): array {
        try {
            $response = $callback($payload);
            $body = $response->json() ?? [];
            $status = $response->status();
        } catch (\Throwable $exception) {
            $this->logAction($targetId, $action, $payload, [
                'error' => $exception->getMessage(),
            ], null, $actor);

            throw $exception;
        }

        $this->logAction($targetId, $action, $payload, $body, $status, $actor);

        return $body;
    }

    protected function logAction(
        string $targetId,
        string $action,
        array $payload,
        array $response,
        ?int $status,
        ?Authenticatable $actor = null,
    ): void {
        InfrastructureActionLog::create([
            'user_id' => $actor?->getAuthIdentifier() ?? auth()->id(),
            'service_id' => $targetId,
            'action' => $action,
            'payload' => $payload,
            'response' => $response,
            'status_code' => $status,
        ]);
    }

    protected function applicationCacheKey(string $applicationId): string
    {
        return "dokploy.application.{$applicationId}";
    }

    protected function deploymentCacheKey(string $applicationId): string
    {
        return "dokploy.deployments.{$applicationId}";
    }

    protected function composeCacheKey(string $composeId): string
    {
        return "dokploy.compose.{$composeId}";
    }

    protected function composeDeploymentsCacheKey(string $composeId): string
    {
        return "dokploy.compose.deployments.{$composeId}";
    }

    protected function forgetApplicationCaches(string $applicationId): void
    {
        Cache::forget($this->applicationCacheKey($applicationId));
        Cache::forget($this->deploymentCacheKey($applicationId));
    }

    protected function forgetComposeCaches(string $composeId): void
    {
        Cache::forget($this->composeCacheKey($composeId));
        Cache::forget($this->composeDeploymentsCacheKey($composeId));
    }
}
