<?php

namespace App\Livewire\Admin;

use App\Models\ExportTarget;
use App\Services\Secrets\SecretCipher;
use LivewireUI\Modal\ModalComponent;

/**
 * Adding or changing one destination.
 *
 * The password never lives here. It goes into the secret vault under a key
 * derived from the target, and only that key is stored on the row — a
 * credential in a settings table is a credential in every database dump. An
 * empty password field on an existing target means "leave it alone", not
 * "clear it": the field cannot show what is stored, so treating blank as a
 * deletion would wipe the credential every time somebody renamed the target.
 */
class EditExportTarget extends ModalComponent
{
    public string $uuid = '';

    public string $name = '';

    public string $driver = ExportTarget::LOCAL;

    public string $path = '';

    /** 'year' for an archive somebody keeps, 'day' for a handover point. */
    public string $layout = ExportTarget::BY_YEAR;

    /** Empty means keep for ever, which is the default and the safe one. */
    public ?int $keepDays = null;

    public string $host = '';

    public ?int $port = 22;

    public string $username = '';

    public string $password = '';

    public bool $hasPassword = false;

    public bool $active = true;

    public function mount(?string $uuid = null): void
    {
        $this->authorize('site.manage'); // modals bypass the route middleware

        if ($uuid === null) {
            return;
        }

        $target = ExportTarget::query()->where('uuid', $uuid)->firstOrFail();

        $this->uuid = $uuid;
        $this->name = $target->name;
        $this->driver = $target->driver;
        $this->path = $target->path;
        $this->layout = $target->layout;
        $this->keepDays = $target->keep_days;
        $this->host = (string) $target->host;
        $this->port = $target->port ?: 22;
        $this->username = (string) $target->username;
        $this->active = (bool) $target->active;
        $this->hasPassword = trim((string) $target->secret_key) !== '';
    }

    public function save()
    {
        $this->authorize('site.manage');

        $rules = [
            'name' => 'required|string|max:120',
            'driver' => 'required|in:local,sftp',
            'path' => 'required|string|max:255',
            'layout' => 'required|in:year,day',
            // Nothing, or a real number of days. Zero would read as "delete
            // immediately" to somebody who typed it meaning "never".
            'keepDays' => 'nullable|integer|min:1|max:3650',
            'active' => 'boolean',
        ];

        if ($this->driver === ExportTarget::SFTP) {
            $rules += [
                'host' => 'required|string|max:190',
                'port' => 'required|integer|min:1|max:65535',
                'username' => 'required|string|max:120',
                // Required only when there is nothing stored yet. Blank on an
                // existing target means "unchanged" — the field cannot show
                // what is stored, so treating blank as a deletion would wipe
                // the credential on every unrelated edit.
                'password' => $this->hasPassword ? 'nullable|string|max:255' : 'required|string|max:255',
            ];
        }

        $data = $this->validate($rules);

        $target = $this->uuid !== ''
            ? ExportTarget::query()->where('uuid', $this->uuid)->firstOrFail()
            : new ExportTarget;

        $target->fill([
            'name' => $data['name'],
            'driver' => $data['driver'],
            'path' => rtrim($data['path'], '/') ?: '/',
            'layout' => $data['layout'],
            'keep_days' => $data['keepDays'] ?: null,
            'host' => $this->driver === ExportTarget::SFTP ? $this->host : null,
            'port' => $this->driver === ExportTarget::SFTP ? $this->port : null,
            'username' => $this->driver === ExportTarget::SFTP ? $this->username : null,
            'active' => $data['active'] ?? true,
        ]);

        $target->save();

        // Encrypted onto the row rather than into the vault. The vault keeps a
        // registry of fixed, documented credentials and refuses anything else,
        // which is right for it — a per-destination password is not one of
        // those, and forcing it in would make the registry meaningless. Same
        // cipher, same key, so it is no more readable in a dump than the rest.
        if ($this->driver === ExportTarget::SFTP && $this->password !== '') {
            $target->forceFill([
                'secret_key' => app(SecretCipher::class)->encrypt($this->password),
            ])->save();
        }

        $this->dispatch('notify', message: __('finance.targets_saved'));

        return $this->redirectRoute('admin.finance', navigate: true);
    }

    public function render()
    {
        return view('livewire.admin.edit-export-target');
    }
}
