<?php

namespace App\Livewire\Modals;

use App\Models\AuditEvent;
use App\Models\Server;
use App\Services\FleetService;
use App\Support\Ssh\Sftp;
use Illuminate\Support\Facades\Auth;
use LivewireUI\Modal\ModalComponent;
use Throwable;

/**
 * View / edit a remote text file over SFTP. Loads lazily (wire:init), refuses
 * binary or oversized files, and writes back on save with an AuditEvent.
 * Image files are shown as a read-only base64 preview instead of the editor.
 */
class FileEditor extends ModalComponent
{
    /** Image preview is capped — larger images are refused (base64 inflates ~33%). */
    private const IMAGE_MAX_BYTES = 5_242_880; // 5 MB

    /** Extensions rendered as an inline image preview (read-only). */
    private const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp', 'ico', 'avif'];

    public int $serverId;

    public string $path;

    public string $name;

    public string $content = '';

    public bool $binary = false;

    public bool $tooBig = false;

    public bool $loaded = false;

    public ?string $error = null;

    /** base64 `data:` URI for image previews; null for text files. */
    public ?string $dataUri = null;

    public function mount(int $serverId, string $path, string $name): void
    {
        $this->serverId = $serverId;
        $this->path = $path;
        $this->name = $name;
    }

    public static function modalMaxWidth(): string
    {
        return 'lg';
    }

    /** True when the opened file is an image (preview, not editable). */
    public function isImage(): bool
    {
        return in_array($this->extension(), self::IMAGE_EXTENSIONS, true);
    }

    private function extension(): string
    {
        return strtolower(pathinfo($this->name, PATHINFO_EXTENSION));
    }

    /** MIME type for the image preview data URI, keyed off the extension. */
    private function imageMime(): string
    {
        return match ($this->extension()) {
            'svg' => 'image/svg+xml',
            'jpg', 'jpeg' => 'image/jpeg',
            'ico' => 'image/x-icon',
            default => 'image/'.$this->extension(),
        };
    }

    public function load(FleetService $fleet, Sftp $sftp): void
    {
        // The actual content read (SFTP get / readFile). Gate it directly — not just the
        // Files::edit entry point — so file bytes the server credential can reach (root →
        // /etc/shadow, keys, .env) are never disclosed to a read-only viewer.
        abort_unless(auth()->user()?->can('operate'), 403);

        $server = Server::find($this->serverId);
        if (! $server) {
            $this->error = __('common.server_not_found');
            $this->loaded = true;

            return;
        }

        try {
            if ($this->isImage()) {
                $this->loadImage($server, $sftp);
            } else {
                $r = $fleet->readFile($server, $this->path);
                $this->binary = $r['binary'];
                $this->tooBig = $r['tooBig'];
                // Never bind raw non-UTF-8 bytes to a public property: Livewire JSON-encodes the
                // snapshot and invalid UTF-8 makes json_encode fail, so the client receives an empty
                // response ("undefined is not valid JSON"). Binary/oversize files show a notice instead.
                $this->content = ($r['binary'] || $r['tooBig']) ? '' : $r['content'];
            }
        } catch (Throwable $e) {
            $this->error = $e->getMessage();
        }

        $this->loaded = true;
    }

    /**
     * Read the image bytes over SFTP and expose them as a base64 data URI.
     * Refuses files larger than IMAGE_MAX_BYTES (checked before download).
     */
    private function loadImage(Server $server, Sftp $sftp): void
    {
        $sftp->connect($server);

        try {
            if ($sftp->size($this->path) > self::IMAGE_MAX_BYTES) {
                $this->tooBig = true;

                return;
            }

            $bytes = $sftp->get($this->path);

            if ($bytes === '') {
                $this->error = __('modals.file_editor.image_read_failed');

                return;
            }

            $this->dataUri = 'data:'.$this->imageMime().';base64,'.base64_encode($bytes);
        } finally {
            $sftp->disconnect();
        }
    }

    public function save(FleetService $fleet): void
    {
        abort_unless(auth()->user()?->can('operate'), 403);

        $this->error = null;

        // Images are a read-only preview — never write them back.
        if ($this->isImage()) {
            return;
        }

        $server = Server::find($this->serverId);
        if (! $server) {
            $this->error = __('common.server_not_found');

            return;
        }

        try {
            $fleet->writeFile($server, $this->path, $this->content);
        } catch (Throwable $e) {
            $this->error = $e->getMessage();

            return;
        }

        AuditEvent::create([
            'user_id' => Auth::id(),
            'server_id' => $server->id,
            'actor' => Auth::user()?->name ?? 'system',
            'action' => 'file.edit',
            'target' => $this->path,
            'ip' => request()->ip(),
        ]);

        $this->dispatch('fileSaved');
        $this->dispatch('notify', message: __('modals.file_editor.notify_saved', ['name' => $this->name]));
        $this->closeModal();
    }

    public function render()
    {
        return view('livewire.modals.file-editor');
    }
}
