<?php

declare(strict_types=1);

namespace App\Domain\Security\Ssh\Services;

use App\Domain\Account\Models\Account;
use App\Domain\Security\Ssh\Exceptions\SshKeyException;
use App\Domain\Sysapi\Contracts\SysapiBridge;
use Illuminate\Support\Facades\DB;

/**
 * SSH self-service provisioner.
 *
 * Sorumluluklar:
 *   - sysapi onx-ssh-key-{add,remove,list} çağırarak gerçek
 *     /home/<user>/.ssh/authorized_keys dosyasına yaz / oku.
 *   - DB tablosu ssh_keys'i bash sonucundan dönen fingerprint ile senkron tut.
 *   - Idempotent: aynı key tekrar eklendiğinde added=false ile geçer.
 */
final class SshKeyProvisioner
{
    private const ALLOWED_TYPES = ['ssh-rsa', 'ssh-ed25519', 'ssh-ecdsa', 'sk-ssh-ed25519'];

    public function __construct(private readonly SysapiBridge $sysapi) {}

    /**
     * Add a public key to the account's authorized_keys file.
     *
     * @return array{
     *   id: int|null,
     *   fingerprint: string,
     *   key_type: string,
     *   total_keys: int,
     *   added: bool
     * }
     *
     * @throws SshKeyException
     */
    public function addKey(Account $account, string $publicKey, ?string $label = null, ?string $comment = null): array
    {
        $key = trim($publicKey);
        if ($key === '') {
            throw SshKeyException::invalidKey('boş anahtar');
        }

        // Quick prefix check (final auth still happens in ssh-keygen on the host)
        $type = explode(' ', $key, 2)[0] ?? '';
        if (! in_array($type, self::ALLOWED_TYPES, true) && ! str_starts_with($type, 'ecdsa-sha2-')) {
            throw SshKeyException::invalidKey("Desteklenmeyen anahtar tipi: {$type}");
        }

        $result = $this->sysapi->run('ssh-key-add', [
            'username' => $account->username,
            'key' => $key,
            'comment' => $comment ?? $label ?? '',
        ]);

        if ($result->failed()) {
            throw SshKeyException::sysapiFailed('add', $result);
        }

        $data = $result->data ?? [];
        $fingerprint = (string) ($data['fingerprint'] ?? '');
        if ($fingerprint === '') {
            throw SshKeyException::sysapiFailed('add', $result);
        }

        $keyType = (string) ($data['key_type'] ?? $type);
        $totalKeys = (int) ($data['total_keys'] ?? 0);
        $added = (bool) ($data['added'] ?? false);

        // DB sync — keep ssh_keys row consistent with what the host actually has.
        $existing = DB::table('ssh_keys')
            ->where('account_id', $account->id)
            ->where('fingerprint', $fingerprint)
            ->whereNull('deleted_at')
            ->first();

        $id = $existing?->id;
        if ($existing === null) {
            $id = DB::table('ssh_keys')->insertGetId([
                'account_id' => $account->id,
                'label' => $label ?? $comment ?? substr($fingerprint, 0, 24),
                'key_type' => in_array($keyType, self::ALLOWED_TYPES, true) ? $keyType : 'ssh-ed25519',
                'public_key' => $key,
                'fingerprint' => $fingerprint,
                'is_authorized' => true,
                'created_at' => now(),
                'updated_at' => now(),
            ]);
        } else {
            DB::table('ssh_keys')->where('id', $existing->id)->update([
                'is_authorized' => true,
                'updated_at' => now(),
            ]);
        }

        return [
            'id' => $id,
            'fingerprint' => $fingerprint,
            'key_type' => $keyType,
            'total_keys' => $totalKeys,
            'added' => $added,
        ];
    }

    /**
     * Remove a key from authorized_keys. Accepts either the fingerprint or
     * the public-key body — bash side recomputes the fingerprint when needed.
     *
     * @return array{removed: bool, remaining_keys: int}
     *
     * @throws SshKeyException
     */
    public function removeKey(Account $account, string $fingerprintOrKey): array
    {
        $args = ['username' => $account->username];
        if (str_starts_with($fingerprintOrKey, 'SHA256:')) {
            $args['fingerprint'] = $fingerprintOrKey;
        } else {
            $args['key'] = trim($fingerprintOrKey);
        }

        $result = $this->sysapi->run('ssh-key-remove', $args);

        if ($result->failed()) {
            throw SshKeyException::sysapiFailed('remove', $result);
        }

        $data = $result->data ?? [];
        $removed = (bool) ($data['removed'] ?? false);
        $remaining = (int) ($data['remaining_keys'] ?? 0);

        // Mark the DB row as soft-deleted when present
        $query = DB::table('ssh_keys')
            ->where('account_id', $account->id)
            ->whereNull('deleted_at');

        if (str_starts_with($fingerprintOrKey, 'SHA256:')) {
            $query->where('fingerprint', $fingerprintOrKey);
        } else {
            $query->where('public_key', trim($fingerprintOrKey));
        }

        $query->update(['deleted_at' => now(), 'is_authorized' => false]);

        return [
            'removed' => $removed,
            'remaining_keys' => $remaining,
        ];
    }

    /**
     * List the live keys from the host's authorized_keys file.
     *
     * @return array{username: string, count: int, keys: array<int, array<string, mixed>>}
     *
     * @throws SshKeyException
     */
    public function listKeys(Account $account): array
    {
        $result = $this->sysapi->run('ssh-key-list', [
            'username' => $account->username,
        ]);

        if ($result->failed()) {
            throw SshKeyException::sysapiFailed('list', $result);
        }

        $data = $result->data ?? [
            'username' => $account->username,
            'count' => 0,
            'keys' => [],
        ];

        return [
            'username' => (string) ($data['username'] ?? $account->username),
            'count' => (int) ($data['count'] ?? 0),
            'keys' => (array) ($data['keys'] ?? []),
        ];
    }
}
