<?php

namespace App\Models;

use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use OpenApi\Attributes as OA;

#[OA\Schema(
    description: 'Server Settings model',
    type: 'object',
    properties: [
        'id' => ['type' => 'integer'],
        'concurrent_builds' => ['type' => 'integer'],
        'deployment_queue_limit' => ['type' => 'integer'],
        'dynamic_timeout' => ['type' => 'integer'],
        'force_disabled' => ['type' => 'boolean'],
        'force_server_cleanup' => ['type' => 'boolean'],
        'is_build_server' => ['type' => 'boolean'],
        'is_cloudflare_tunnel' => ['type' => 'boolean'],
        'is_jump_server' => ['type' => 'boolean'],
        'is_logdrain_axiom_enabled' => ['type' => 'boolean'],
        'is_logdrain_custom_enabled' => ['type' => 'boolean'],
        'is_logdrain_highlight_enabled' => ['type' => 'boolean'],
        'is_logdrain_newrelic_enabled' => ['type' => 'boolean'],
        'is_metrics_enabled' => ['type' => 'boolean'],
        'is_reachable' => ['type' => 'boolean'],
        'is_sentinel_enabled' => ['type' => 'boolean'],
        'is_swarm_manager' => ['type' => 'boolean'],
        'is_swarm_worker' => ['type' => 'boolean'],
        'is_terminal_enabled' => ['type' => 'boolean'],
        'is_usable' => ['type' => 'boolean'],
        'logdrain_axiom_api_key' => ['type' => 'string'],
        'logdrain_axiom_dataset_name' => ['type' => 'string'],
        'logdrain_custom_config' => ['type' => 'string'],
        'logdrain_custom_config_parser' => ['type' => 'string'],
        'logdrain_highlight_project_id' => ['type' => 'string'],
        'logdrain_newrelic_base_uri' => ['type' => 'string'],
        'logdrain_newrelic_license_key' => ['type' => 'string'],
        'sentinel_metrics_history_days' => ['type' => 'integer'],
        'sentinel_metrics_refresh_rate_seconds' => ['type' => 'integer'],
        'sentinel_token' => ['type' => 'string'],
        'docker_cleanup_frequency' => ['type' => 'string'],
        'docker_cleanup_threshold' => ['type' => 'integer'],
        'server_id' => ['type' => 'integer'],
        'wildcard_domain' => ['type' => 'string'],
        'created_at' => ['type' => 'string'],
        'updated_at' => ['type' => 'string'],
        'delete_unused_volumes' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused volumes should be deleted.'],
        'delete_unused_networks' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused networks should be deleted.'],
        'connection_timeout' => ['type' => 'integer', 'description' => 'SSH connection timeout in seconds.'],
    ]
)]
class ServerSetting extends Model
{
    protected $fillable = [
        'server_id',
        'is_swarm_manager',
        'is_jump_server',
        'is_build_server',
        'is_reachable',
        'is_usable',
        'wildcard_domain',
        'is_cloudflare_tunnel',
        'is_logdrain_newrelic_enabled',
        'logdrain_newrelic_license_key',
        'logdrain_newrelic_base_uri',
        'is_logdrain_highlight_enabled',
        'logdrain_highlight_project_id',
        'is_logdrain_axiom_enabled',
        'logdrain_axiom_dataset_name',
        'logdrain_axiom_api_key',
        'is_swarm_worker',
        'is_logdrain_custom_enabled',
        'logdrain_custom_config',
        'logdrain_custom_config_parser',
        'concurrent_builds',
        'dynamic_timeout',
        'force_disabled',
        'is_metrics_enabled',
        'generate_exact_labels',
        'force_docker_cleanup',
        'docker_cleanup_frequency',
        'docker_cleanup_threshold',
        'server_timezone',
        'delete_unused_volumes',
        'delete_unused_networks',
        'is_sentinel_enabled',
        'sentinel_token',
        'sentinel_metrics_refresh_rate_seconds',
        'sentinel_metrics_history_days',
        'sentinel_push_interval_seconds',
        'sentinel_custom_url',
        'server_disk_usage_notification_threshold',
        'is_sentinel_debug_enabled',
        'server_disk_usage_check_frequency',
        'is_terminal_enabled',
        'deployment_queue_limit',
        'disable_application_image_retention',
        'connection_timeout',
    ];

    protected $casts = [
        'force_disabled' => 'boolean',
        'force_docker_cleanup' => 'boolean',
        'docker_cleanup_threshold' => 'integer',
        'sentinel_token' => 'encrypted',
        'is_reachable' => 'boolean',
        'is_usable' => 'boolean',
        'is_terminal_enabled' => 'boolean',
        'disable_application_image_retention' => 'boolean',
        'connection_timeout' => 'integer',
    ];

    protected static function booted()
    {
        static::creating(function ($setting) {
            try {
                if (str($setting->sentinel_token)->isEmpty()) {
                    $setting->generateSentinelToken(save: false, ignoreEvent: true);
                }
                if (str($setting->sentinel_custom_url)->isEmpty()) {
                    $setting->generateSentinelUrl(save: false, ignoreEvent: true);
                }
            } catch (\Throwable $e) {
                Log::error('Error creating server setting: '.$e->getMessage());
            }
        });
        static::updated(function ($settings) {
            if (
                $settings->wasChanged('sentinel_token') ||
                $settings->wasChanged('sentinel_custom_url') ||
                $settings->wasChanged('sentinel_metrics_refresh_rate_seconds') ||
                $settings->wasChanged('sentinel_metrics_history_days') ||
                $settings->wasChanged('sentinel_push_interval_seconds')
            ) {
                $settings->server->restartSentinel();
            }
        });
    }

    /**
     * Validate that a sentinel token contains only safe characters.
     * Prevents OS command injection when the token is interpolated into shell commands.
     */
    public static function isValidSentinelToken(?string $token): bool
    {
        if ($token === null) {
            return false;
        }

        return (bool) preg_match('/\A[a-zA-Z0-9._\-+=\/]+\z/', $token);
    }

    /**
     * Returns a valid sentinel token, regenerating it if the stored value is
     * empty, undecryptable, or otherwise invalid. Throws only when regeneration
     * still fails to produce a valid token.
     */
    public function ensureValidSentinelToken(): string
    {
        try {
            $token = $this->sentinel_token;
        } catch (DecryptException) {
            $token = null;
        }

        if (! self::isValidSentinelToken($token)) {
            // Clear undecryptable raw value so Eloquent's dirty-check won't try to
            // decrypt the bad original during save().
            $attrs = $this->getAttributes();
            $attrs['sentinel_token'] = null;
            $this->setRawAttributes($attrs, true);

            $this->generateSentinelToken(save: true, ignoreEvent: true);
            $this->refresh();
            $token = $this->sentinel_token;
        }

        if (! self::isValidSentinelToken($token)) {
            throw new \RuntimeException('Sentinel token invalid after regeneration. Allowed characters: a-z, A-Z, 0-9, dot, underscore, hyphen, plus, slash, equals.');
        }

        return $token;
    }

    public function generateSentinelToken(bool $save = true, bool $ignoreEvent = false): string
    {
        $data = [
            'server_uuid' => $this->server->uuid,
        ];
        $token = encrypt(json_encode($data));
        $this->sentinel_token = $token;
        if ($save) {
            if ($ignoreEvent) {
                $this->saveQuietly();
            } else {
                $this->save();
            }
        }

        return $token;
    }

    public function generateSentinelUrl(bool $save = true, bool $ignoreEvent = false)
    {
        $domain = null;
        $settings = InstanceSettings::get();
        if ($this->server->isLocalhost()) {
            $domain = 'http://host.docker.internal:8000';
        } elseif ($settings->fqdn) {
            $domain = $settings->fqdn;
        } elseif ($settings->public_ipv4) {
            $domain = 'http://'.$settings->public_ipv4.':8000';
        } elseif ($settings->public_ipv6) {
            $domain = 'http://'.$settings->public_ipv6.':8000';
        }
        $this->sentinel_custom_url = $domain;
        if ($save) {
            if ($ignoreEvent) {
                $this->saveQuietly();
            } else {
                $this->save();
            }
        }

        return $domain;
    }

    public function server()
    {
        return $this->belongsTo(Server::class);
    }

    public function dockerCleanupFrequency(): Attribute
    {
        return Attribute::make(
            set: function ($value) {
                return translate_cron_expression($value);
            },
            get: function ($value) {
                return translate_cron_expression($value);
            }
        );
    }
}
