<?php

namespace App\ServerProviders;

use App\Exceptions\CouldNotConnectToProvider;
use App\Exceptions\ServerProviderError;
use App\Facades\Notifier;
use App\Notifications\FailedToDeleteServerFromProvider;
use Exception;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;

class Vultr extends AbstractProvider
{
    protected string $apiUrl = 'https://api.vultr.com/v2';

    public function createRules($input): array
    {
        $rules = [];
        // plans
        $plans = [];
        foreach (config('serverproviders.vultr.plans') as $plan) {
            $plans[] = $plan['value'];
        }
        $rules['plan'] = 'required|in:'.implode(',', $plans);
        // regions
        $regions = [];
        foreach (config('serverproviders.vultr.regions') as $region) {
            $regions[] = $region['value'];
        }
        $rules['region'] = 'required|in:'.implode(',', $regions);

        return $rules;
    }

    public function credentialValidationRules($input): array
    {
        return [
            'token' => 'required',
        ];
    }

    public function credentialData($input): array
    {
        return [
            'token' => $input['token'],
        ];
    }

    public function data(array $input): array
    {
        return [
            'plan' => $input['plan'],
            'region' => $input['region'],
        ];
    }

    /**
     * @throws CouldNotConnectToProvider
     */
    public function connect(?array $credentials = null): bool
    {
        $connect = Http::withToken($credentials['token'])->get($this->apiUrl.'/account');
        if (! $connect->ok()) {
            throw new CouldNotConnectToProvider('Vultr');
        }

        return true;
    }

    public function plans(?string $region): array
    {
        return collect(config('serverproviders.vultr.plans'))
            ->mapWithKeys(fn ($value) => [$value['value'] => $value['title']])
            ->toArray();
    }

    public function regions(): array
    {
        return collect(config('serverproviders.vultr.regions'))
            ->mapWithKeys(fn ($value) => [$value['value'] => $value['title']])
            ->toArray();
    }

    /**
     * @throws ServerProviderError
     */
    public function create(): void
    {
        // generate key pair
        /** @var \Illuminate\Filesystem\FilesystemAdapter $storageDisk */
        $storageDisk = Storage::disk(config('core.key_pairs_disk'));
        generate_key_pair($storageDisk->path((string) $this->server->id));

        $createSshKey = Http::withToken($this->server->serverProvider->credentials['token'])
            ->post($this->apiUrl.'/ssh-keys', [
                'ssh_key' => $this->server->sshKey()['public_key'],
                'name' => $this->server->name.'_'.$this->server->id,
            ]);
        if ($createSshKey->status() != 201) {
            throw new ServerProviderError('Error creating SSH Key on Vultr');
        }

        $create = Http::withToken($this->server->serverProvider->credentials['token'])
            ->post($this->apiUrl.'/instances', [
                'label' => $this->server->name,
                'region' => $this->server->provider_data['region'],
                'plan' => $this->server->provider_data['plan'],
                'os_id' => config('serverproviders.vultr.images')[$this->server->os],
                'enable_ipv6' => false,
                'sshkey_id' => [$createSshKey->json()['ssh_key']['id']],
            ]);
        if ($create->status() != 202) {
            $msg = __('Failed to create server on Vultr');
            Log::error('Failed to create server on Vultr', $create->json());
            throw new ServerProviderError($msg);
        }
        $providerData = $this->server->provider_data;
        $providerData['instance_id'] = $create->json()['instance']['id'];
        $this->server->provider_data = $providerData;
        $this->server->save();
    }

    public function isRunning(): bool
    {
        $status = Http::withToken($this->server->serverProvider->credentials['token'])
            ->get($this->apiUrl.'/instances/'.$this->server->provider_data['instance_id']);

        if (! $status->ok()) {
            return false;
        }

        if (! $this->server->ip) {
            $this->server->ip = $status->json()['instance']['main_ip'];
            $this->server->save();
        }

        return $status->json()['instance']['status'] == 'active';
    }

    /**
     * @throws Exception
     */
    public function delete(): void
    {
        if (isset($this->server->provider_data['instance_id'])) {
            $delete = Http::withToken($this->server->serverProvider->credentials['token'])
                ->delete($this->apiUrl.'/instances/'.$this->server->provider_data['instance_id']);

            if (! $delete->ok()) {
                Notifier::send($this->server, new FailedToDeleteServerFromProvider($this->server));
            }
        }
    }
}
