<?php

declare(strict_types=1);

namespace PpPhp\Core\Deploy;

use PpPhp\Core\DB;

/**
 * Persists FTP/FTPS/SFTP connection profiles in `deploy_connections`, one set
 * per project, with passwords and key passphrases encrypted at rest.
 *
 * Two views of a profile exist:
 *   - forClient(): safe to send to the browser (no secrets, just presence flags)
 *   - transportProfile(): full record with decrypted secrets, server-side only
 */
class ConnectionRepository
{
    public function __construct(private int $projectId)
    {
        DeploySchema::ensureSchema();
        DB::Init();
        DB::SetConnection();
    }

    /** All profiles for the project, sanitised for the browser. */
    public function forClient(): array
    {
        $rows = DB::FetchAll(
            'SELECT * FROM `deploy_connections` WHERE `project_id` = ? ORDER BY `name`',
            [$this->projectId]
        ) ?: [];
        return array_map([$this, 'sanitise'], $rows);
    }

    /** A single sanitised profile, or null. */
    public function getForClient(int $id): ?array
    {
        $row = $this->row($id);
        return $row ? $this->sanitise($row) : null;
    }

    /** Full transport-ready profile (decrypted secrets). Server-side only. */
    public function transportProfile(int $id): ?array
    {
        $row = $this->row($id);
        return $row ? $this->toTransportProfile($row) : null;
    }

    /**
     * Insert or update a profile. On update, a blank password/key-pass means
     * "keep the stored secret". Returns the connection id.
     *
     * @param array $in fields from the client editor
     */
    public function save(array $in): int
    {
        $id = (int) ($in['id'] ?? 0);
        $existing = $id > 0 ? $this->row($id) : null;

        $protocol = in_array(($in['protocol'] ?? 'ftp'), ['ftp', 'ftps', 'sftp'], true) ? $in['protocol'] : 'ftp';
        $port = (int) ($in['port'] ?? 0);
        if ($port <= 0) {
            $port = $protocol === 'sftp' ? 22 : 21;
        }

        // Secrets: only re-encrypt when the client sent a non-empty value.
        $passEnc = ($in['pass'] ?? '') !== ''
            ? DeploySecret::encrypt((string) $in['pass'])
            : ($existing['password_enc'] ?? '');
        $keyPassEnc = ($in['privateKeyPass'] ?? '') !== ''
            ? DeploySecret::encrypt((string) $in['privateKeyPass'])
            : ($existing['private_key_pass_enc'] ?? '');

        $data = [
            'project_id'           => $this->projectId,
            'name'                 => trim((string) ($in['name'] ?? 'Connection')),
            'protocol'             => $protocol,
            'host'                 => trim((string) ($in['host'] ?? '')),
            'port'                 => $port,
            'username'             => (string) ($in['user'] ?? ''),
            'password_enc'         => $passEnc,
            'remote_path'          => trim((string) ($in['remotePath'] ?? '')),
            'site_url'             => trim((string) ($in['siteUrl'] ?? '')),
            'passive'              => !empty($in['passive']) ? 1 : 0,
            'private_key_file'     => trim((string) ($in['privateKeyFile'] ?? '')),
            'private_key_pass_enc' => $keyPassEnc,
        ];

        if ($existing) {
            DB::Update('deploy_connections', $data, ['id' => $id, 'project_id' => $this->projectId]);
            return $id;
        }
        return (int) DB::Insert('deploy_connections', $data);
    }

    public function delete(int $id): bool
    {
        return (bool) DB::Delete('deploy_connections', ['id' => $id, 'project_id' => $this->projectId]);
    }

    /** Record that a profile was just used for a transfer. */
    public function touch(int $id): void
    {
        DB::Update('deploy_connections', ['last_used_at' => date('Y-m-d H:i:s')], ['id' => $id, 'project_id' => $this->projectId]);
    }

    /**
     * One-time import of profiles that were previously stored in ModuleState
     * JSON, so existing setups aren't lost. Returns the number imported.
     */
    public function importFromState(array $stateProfiles): int
    {
        if ($stateProfiles === []) {
            return 0;
        }
        $count = (int) DB::FetchOne('SELECT COUNT(*) FROM `deploy_connections` WHERE `project_id` = ?', [$this->projectId]);
        if ($count > 0) {
            return 0; // already migrated / has its own data
        }
        $imported = 0;
        foreach ($stateProfiles as $p) {
            if (empty($p['host'])) {
                continue;
            }
            $this->save([
                'name'           => $p['name'] ?? $p['host'],
                'protocol'       => $p['protocol'] ?? 'ftp',
                'host'           => $p['host'] ?? '',
                'port'           => $p['port'] ?? 0,
                'user'           => $p['user'] ?? '',
                'pass'           => $p['pass'] ?? '',
                'remotePath'     => $p['remotePath'] ?? '',
                'siteUrl'        => $p['siteUrl'] ?? '',
                'passive'        => $p['passive'] ?? true,
                'privateKeyFile' => $p['privateKeyFile'] ?? '',
                'privateKeyPass' => $p['privateKeyPass'] ?? '',
            ]);
            $imported++;
        }
        return $imported;
    }

    // ── internals ───────────────────────────────────────────────────

    private function row(int $id): ?array
    {
        $rows = DB::Select('deploy_connections', ['id' => $id, 'project_id' => $this->projectId]);
        return $rows[0] ?? null;
    }

    /** DB row → browser-safe array (no secrets). */
    private function sanitise(array $r): array
    {
        return [
            'id'             => (int) $r['id'],
            'name'           => $r['name'],
            'protocol'       => $r['protocol'],
            'host'           => $r['host'],
            'port'           => (int) $r['port'],
            'user'           => $r['username'],
            'remotePath'     => $r['remote_path'],
            'siteUrl'        => $r['site_url'],
            'passive'        => (int) $r['passive'] === 1,
            'privateKeyFile' => $r['private_key_file'],
            'hasPassword'    => ($r['password_enc'] ?? '') !== '',
            'hasKeyPass'     => ($r['private_key_pass_enc'] ?? '') !== '',
            'lastUsedAt'     => $r['last_used_at'],
        ];
    }

    /** DB row → TransportFactory-ready profile (decrypted). */
    private function toTransportProfile(array $r): array
    {
        return [
            'id'             => (int) $r['id'],
            'name'           => $r['name'],
            'protocol'       => $r['protocol'],
            'host'           => $r['host'],
            'port'           => (int) $r['port'],
            'user'           => $r['username'],
            'pass'           => DeploySecret::decrypt((string) ($r['password_enc'] ?? '')),
            'remotePath'     => $r['remote_path'],
            'siteUrl'        => $r['site_url'],
            'passive'        => (int) $r['passive'] === 1,
            'privateKeyFile' => $r['private_key_file'],
            'privateKeyPass' => DeploySecret::decrypt((string) ($r['private_key_pass_enc'] ?? '')),
        ];
    }
}
