<?php
/**
 * Destination record — DAL for wp_backreload_destinations.
 *
 * @package Backreload\Models
 */

namespace Backreload\Models;

if ( ! defined( 'ABSPATH' ) ) { exit; }

class DestinationRecord {

	public ?int    $id          = null;
	public string  $ext_id      = '';
	public string  $type        = 'local';
	public string  $name        = '';
	public array   $config      = [];
	public string  $status      = 'disconnected';
	/** User-controlled "skip me for now" flag. Paused destinations are filtered
	 *  out by the backup engine and the retention sweep, but their config (and
	 *  any already-uploaded backups) are preserved. */
	public bool    $paused      = false;
	public ?string $last_tested = null;
	public ?string $created_at  = null;

	public static function table(): string {
		global $wpdb;
		return $wpdb->prefix . 'backreload_destinations';
	}

	public static function generate_id(): string {
		return 'd_' . wp_generate_password( 8, false, false );
	}

	public static function create( array $data ): self {
		$d              = new self();
		$d->ext_id      = $data['ext_id'] ?? self::generate_id();
		$d->type        = (string) ( $data['type']   ?? 'local' );
		$d->name        = (string) ( $data['name']   ?? ucfirst( $d->type ) );
		$d->config      = (array)  ( $data['config'] ?? [] );
		$d->status      = (string) ( $data['status'] ?? 'disconnected' );
		$d->created_at  = current_time( 'mysql', true );
		$d->save();
		return $d;
	}

	public function save(): void {
		global $wpdb;

		// Encrypt secrets in $config before persisting. The encrypt helper is
		// idempotent (won't double-encrypt already-encrypted values).
		$enc_config = $this->config;
		if ( function_exists( 'backreload' ) ) {
			try {
				$enc_config = \backreload()->get( 'encryption' )->encrypt_config( $this->config );
			} catch ( \Throwable $e ) { /* fall back to plaintext rather than lose data */ }
		}

		$row = [
			'ext_id'      => $this->ext_id,
			'type'        => $this->type,
			'name'        => $this->name,
			'config'      => wp_json_encode( $enc_config ),
			'status'      => $this->status,
			'paused'      => $this->paused ? 1 : 0,
			'last_tested' => $this->last_tested,
			'created_at'  => $this->created_at ?: current_time( 'mysql', true ),
		];
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Plugin reads/writes its own custom tables; results are transient backup state and must not be cached.
		if ( $this->id ) {
			$wpdb->update( self::table(), $row, [ 'id' => $this->id ] );
		} else {
			$wpdb->insert( self::table(), $row );
			$this->id = (int) $wpdb->insert_id;
		}
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
	}

	public function delete(): void {
		global $wpdb;
		if ( $this->id ) {
			$wpdb->delete( self::table(), [ 'id' => $this->id ] ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Plugin reads/writes its own custom tables; results are transient backup state and must not be cached.
		}
	}

	public static function find( string $ext_id ): ?self {
		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Plugin reads/writes its own custom tables; results are transient backup state and must not be cached. Interpolated identifier is a table name derived from $wpdb->prefix (never user input) and cannot be bound via prepare(); all data values use %s/%d placeholders.
		$row = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM ' . self::table() . ' WHERE ext_id = %s', $ext_id ), ARRAY_A );
		return $row ? self::from_row( $row ) : null;
	}

	/** @return self[] */
	public static function all(): array {
		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Plugin reads/writes its own custom tables; results are transient backup state and must not be cached. Interpolated identifier is a table name derived from $wpdb->prefix (never user input) and cannot be bound via prepare(); all data values use %s/%d placeholders.
		$rows = $wpdb->get_results( 'SELECT * FROM ' . self::table() . ' ORDER BY id ASC', ARRAY_A );
		return array_map( [ self::class, 'from_row' ], $rows ?: [] );
	}

	public static function from_row( array $row ): self {
		$d              = new self();
		$d->id          = (int) $row['id'];
		$d->ext_id      = (string) $row['ext_id'];
		$d->type        = (string) $row['type'];
		$d->name        = (string) $row['name'];
		$config         = json_decode( $row['config'] ?? '[]', true ) ?: [];
		// Transparently decrypt any encrypted secret values. Legacy plaintext
		// rows pass through untouched (decrypt_string checks the prefix).
		if ( function_exists( 'backreload' ) ) {
			try {
				$config = \backreload()->get( 'encryption' )->decrypt_config( $config );
			} catch ( \Throwable $e ) { /* leave config as-is */ }
		}
		$d->config      = $config;
		$d->status      = (string) $row['status'];
		$d->paused      = ! empty( $row['paused'] );
		$d->last_tested = $row['last_tested'];
		$d->created_at  = $row['created_at'];
		return $d;
	}

	public function to_array( bool $with_secrets = false ): array {
		$config = $this->config;
		if ( ! $with_secrets ) {
			// Redact using Encryption::is_secret_field() as the single source of
			// truth — a hand-maintained list here previously drifted out of sync
			// and leaked OAuth `access_token` / `client_secret` to the browser
			// (they were encrypted at rest but decrypted into REST responses).
			foreach ( $config as $k => $v ) {
				if ( \Backreload\Core\Encryption::is_secret_field( (string) $k ) ) {
					$config[ $k ] = $v ? '***' : '';
				}
			}
		}
		return [
			'id'          => $this->ext_id,
			'type'        => $this->type,
			'name'        => $this->name,
			'status'      => $this->status,
			'paused'      => $this->paused,
			'config'      => $config,
			'last_tested' => $this->last_tested,
			'created_at'  => $this->created_at,
		];
	}
}
