<?php

declare(strict_types=1);

namespace App\Services\Provisioning;

use App\Models\ProvisioningLog;
use App\Models\Service;
use App\Notifications\ServiceCredentialsNotification;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Laravel\Cashier\Subscription;
use RuntimeException;

class PterodactylService implements ProvisioningServiceInterface
{
    private readonly string $baseUrl;

    private readonly string $apiKey;

    public function __construct()
    {
        // Read from database settings (configured via admin panel)
        $this->baseUrl = rtrim(\App\Models\Setting::get('pterodactyl_api_url', ''), '/');
        $this->apiKey = \App\Models\Setting::get('pterodactyl_api_token', '');
    }

    public function provision(Subscription $subscription): Service
    {
        $plan = $subscription->plan;
        $user = $subscription->user;

        if (! $plan) {
            throw new RuntimeException('Subscription has no associated plan.');
        }

        $service = Service::firstOrCreate(
            ['subscription_id' => $subscription->id, 'service_type' => 'game'],
            [
                'user_id' => $user->id,
                'plan_id' => $plan->id,
                'platform' => 'pterodactyl',
                'status' => 'pending',
            ],
        );

        if ($service->status === 'failed') {
            $service->update(['status' => 'pending']);
        }

        $this->logAction($service, 'provision', 'pending');

        try {
            $features = $plan->features ?? [];
            $password = Str::random(16);

            // First, ensure the user exists on the Pterodactyl panel or create them
            $panelUserId = $this->findOrCreatePanelUser($user);

            $response = $this->client()->post('/api/application/servers', [
                'name' => $features['server_name'] ?? "ezscale-{$service->id}",
                'user' => $panelUserId,
                'egg' => $features['egg_id'] ?? 1,
                'docker_image' => $features['docker_image'] ?? 'ghcr.io/pterodactyl/yolks:java_17',
                'startup' => $features['startup_command'] ?? 'java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}',
                'environment' => $features['environment'] ?? [
                    'SERVER_JARFILE' => 'server.jar',
                    'VANILLA_VERSION' => 'latest',
                ],
                'limits' => [
                    'memory' => $features['memory'] ?? 1024,
                    'swap' => $features['swap'] ?? 0,
                    'disk' => $features['disk'] ?? 10240,
                    'io' => $features['io'] ?? 500,
                    'cpu' => $features['cpu'] ?? 100,
                ],
                'feature_limits' => [
                    'databases' => $features['databases'] ?? 1,
                    'backups' => $features['backups'] ?? 1,
                    'allocations' => $features['allocations'] ?? 1,
                ],
                'allocation' => [
                    'default' => $features['allocation_id'] ?? 1,
                ],
                'deploy' => isset($features['allocation_id']) ? null : [
                    'locations' => $features['location_ids'] ?? [1],
                    'dedicated_ip' => $features['dedicated_ip'] ?? false,
                    'port_range' => $features['port_range'] ?? [],
                ],
                'start_on_completion' => $features['start_on_completion'] ?? true,
            ]);

            if (! $response->successful()) {
                $this->logAction($service, 'provision', 'failed', $response->json(), $response->body());

                throw new RuntimeException("Pterodactyl provisioning failed: {$response->body()}");
            }

            $data = $response->json();
            $serverData = $data['attributes'] ?? $data;
            $serverId = (string) ($serverData['id'] ?? '');

            // Extract IP and port from the allocation relationships
            $allocation = $serverData['relationships']['allocations']['data'][0]['attributes'] ?? [];
            $ipAddress = $allocation['ip'] ?? null;
            $port = $allocation['port'] ?? null;

            $service->update([
                'platform_service_id' => $serverId,
                'status' => 'active',
                'ipv4_address' => $ipAddress,
                'hostname' => $ipAddress ? "{$ipAddress}:{$port}" : null,
                'provisioned_at' => now(),
                'credentials' => [
                    'panel_user_id' => $panelUserId,
                    'server_identifier' => $serverData['identifier'] ?? null,
                ],
            ]);

            $this->logAction($service, 'provision', 'success', $data);

            $service = $service->fresh();

            if ($service->user) {
                $service->user->notify(new ServiceCredentialsNotification($service, [
                    'username' => $user->email,
                    'password' => 'Use your panel login credentials',
                    'hostname' => $service->hostname ?? $service->ipv4_address ?? 'N/A',
                    'ip_address' => $service->ipv4_address ?? 'Pending',
                    'port' => $port,
                    'panel_url' => "{$this->baseUrl}/server/{$serverData['identifier']}",
                ]));
            }

            return $service;
        } catch (RuntimeException $e) {
            throw $e;
        } catch (\Exception $e) {
            $this->logAction($service, 'provision', 'failed', errorMessage: $e->getMessage());

            $service->update(['status' => 'failed']);

            throw new RuntimeException("Pterodactyl provisioning error: {$e->getMessage()}", 0, $e);
        }
    }

    public function suspend(Service $service): bool
    {
        $this->validateServicePlatform($service);

        $this->logAction($service, 'suspend', 'pending');

        try {
            $response = $this->client()->post("/api/application/servers/{$service->platform_service_id}/suspend");

            if (! $response->successful()) {
                $this->logAction($service, 'suspend', 'failed', $response->json(), $response->body());

                return false;
            }

            $service->update([
                'status' => 'suspended',
                'suspended_at' => now(),
            ]);

            $this->logAction($service, 'suspend', 'success');

            return true;
        } catch (\Exception $e) {
            Log::error('Pterodactyl suspend failed', [
                'service_id' => $service->id,
                'error' => $e->getMessage(),
            ]);

            $this->logAction($service, 'suspend', 'failed', errorMessage: $e->getMessage());

            return false;
        }
    }

    public function unsuspend(Service $service): bool
    {
        $this->validateServicePlatform($service);

        $this->logAction($service, 'unsuspend', 'pending');

        try {
            $response = $this->client()->post("/api/application/servers/{$service->platform_service_id}/unsuspend");

            if (! $response->successful()) {
                $this->logAction($service, 'unsuspend', 'failed', $response->json(), $response->body());

                return false;
            }

            $service->update([
                'status' => 'active',
                'suspended_at' => null,
            ]);

            $this->logAction($service, 'unsuspend', 'success');

            return true;
        } catch (\Exception $e) {
            Log::error('Pterodactyl unsuspend failed', [
                'service_id' => $service->id,
                'error' => $e->getMessage(),
            ]);

            $this->logAction($service, 'unsuspend', 'failed', errorMessage: $e->getMessage());

            return false;
        }
    }

    public function terminate(Service $service): bool
    {
        $this->validateServicePlatform($service);

        $this->logAction($service, 'terminate', 'pending');

        try {
            $response = $this->client()->delete("/api/application/servers/{$service->platform_service_id}");

            if (! $response->successful()) {
                $this->logAction($service, 'terminate', 'failed', $response->json(), $response->body());

                return false;
            }

            $service->update([
                'status' => 'terminated',
                'terminated_at' => now(),
            ]);

            $this->logAction($service, 'terminate', 'success');

            return true;
        } catch (\Exception $e) {
            Log::error('Pterodactyl terminate failed', [
                'service_id' => $service->id,
                'error' => $e->getMessage(),
            ]);

            $this->logAction($service, 'terminate', 'failed', errorMessage: $e->getMessage());

            return false;
        }
    }

    /**
     * @return array{status: string, ip_address?: string, hostname?: string, platform_data?: array<string, mixed>}
     */
    public function getStatus(Service $service): array
    {
        $this->validateServicePlatform($service);

        try {
            $response = $this->client()->get("/api/application/servers/{$service->platform_service_id}", [
                'include' => 'allocations',
            ]);

            if (! $response->successful()) {
                return ['status' => 'unknown'];
            }

            $data = $response->json();
            $serverData = $data['attributes'] ?? $data;

            // Map Pterodactyl statuses to our internal statuses
            $status = match (true) {
                ($serverData['suspended'] ?? false) => 'suspended',
                isset($serverData['status']) && $serverData['status'] === 'installing' => 'installing',
                default => 'active',
            };

            // Try to get resource usage from the client API
            $resourceData = $this->getResourceUsage($service);

            return [
                'status' => $status,
                'ip_address' => $service->ipv4_address,
                'hostname' => $service->hostname,
                'platform_data' => array_merge($serverData, [
                    'resources' => $resourceData,
                ]),
            ];
        } catch (\Exception $e) {
            Log::error('Pterodactyl getStatus failed', [
                'service_id' => $service->id,
                'error' => $e->getMessage(),
            ]);

            return ['status' => 'unknown'];
        }
    }

    /**
     * @return array{username?: string, password?: string, url?: string, additional?: array<string, mixed>}
     */
    public function getCredentials(Service $service): array
    {
        $this->validateServicePlatform($service);

        $stored = $service->credentials ?? [];
        $identifier = $stored['server_identifier'] ?? null;

        return [
            'username' => $service->user?->email ?? 'see panel',
            'password' => null,
            'url' => $identifier ? "{$this->baseUrl}/server/{$identifier}" : $this->baseUrl,
            'additional' => [
                'ip_address' => $service->ipv4_address,
                'hostname' => $service->hostname,
                'panel_url' => $this->baseUrl,
                'server_identifier' => $identifier,
            ],
        ];
    }

    /**
     * Get resource usage for a server.
     *
     * @return array<string, mixed>
     */
    private function getResourceUsage(Service $service): array
    {
        try {
            $identifier = $service->credentials['server_identifier'] ?? null;

            if (! $identifier) {
                return [];
            }

            $response = $this->client()->get("/api/application/servers/{$service->platform_service_id}");

            if (! $response->successful()) {
                return [];
            }

            $data = $response->json();
            $attributes = $data['attributes'] ?? [];
            $limits = $attributes['limits'] ?? [];

            return [
                'memory_limit' => $limits['memory'] ?? 0,
                'disk_limit' => $limits['disk'] ?? 0,
                'cpu_limit' => $limits['cpu'] ?? 0,
                'io_limit' => $limits['io'] ?? 0,
            ];
        } catch (\Exception) {
            return [];
        }
    }

    /**
     * Find or create a user on the Pterodactyl panel.
     *
     * @param  \App\Models\User  $user
     */
    private function findOrCreatePanelUser($user): int
    {
        // Search for existing user by email
        $response = $this->client()->get('/api/application/users', [
            'filter[email]' => $user->email,
        ]);

        if ($response->successful()) {
            $data = $response->json();
            $users = $data['data'] ?? [];

            foreach ($users as $panelUser) {
                if (($panelUser['attributes']['email'] ?? '') === $user->email) {
                    return (int) $panelUser['attributes']['id'];
                }
            }
        }

        // Create new user on the panel
        $createResponse = $this->client()->post('/api/application/users', [
            'email' => $user->email,
            'username' => Str::slug($user->name).'-'.$user->id,
            'first_name' => $user->name,
            'last_name' => 'EZSCALE',
        ]);

        if (! $createResponse->successful()) {
            throw new RuntimeException("Failed to create Pterodactyl panel user: {$createResponse->body()}");
        }

        $createData = $createResponse->json();

        return (int) ($createData['attributes']['id'] ?? $createData['id'] ?? 0);
    }

    private function client(): PendingRequest
    {
        return Http::withHeaders([
            'Authorization' => "Bearer {$this->apiKey}",
        ])
            ->baseUrl($this->baseUrl)
            ->acceptJson()
            ->contentType('application/json')
            ->timeout(30);
    }

    private function validateServicePlatform(Service $service): void
    {
        if (! $service->platform_service_id) {
            throw new RuntimeException('Service has no platform service ID.');
        }
    }

    /**
     * @param  array<string, mixed>|null  $response
     */
    private function logAction(
        Service $service,
        string $action,
        string $status,
        ?array $response = null,
        ?string $errorMessage = null,
    ): void {
        ProvisioningLog::create([
            'service_id' => $service->id,
            'user_id' => $service->user_id,
            'action' => $action,
            'platform' => 'pterodactyl',
            'platform_response' => $response,
            'status' => $status,
            'error_message' => $errorMessage,
        ]);
    }
}
