<?php
namespace R2_Offload\Storage;

use R2_Offload\Logger;
use R2_Offload\Settings;

defined( 'ABSPATH' ) || exit;

/**
 * Bunny.net Edge Storage client. Not S3-compatible — uses Bunny's own HTTP API
 * with a single AccessKey header. Transport is the WordPress HTTP API.
 */
final class Bunny_Client implements Storage_Client {

	private Settings $settings;
	private Logger $logger;
	private Rate_Limiter $limiter;

	public function __construct( Settings $settings, Logger $logger ) {
		$this->settings = $settings;
		$this->logger   = $logger;
		$this->limiter  = new Rate_Limiter( $settings, $logger );
	}

	public function is_available(): bool {
		return $this->settings->is_configured();
	}

	private function host(): string {
		$region = trim( (string) $this->settings->get( 'bunny_region' ) );
		return $region !== '' ? $region . '.storage.bunnycdn.com' : 'storage.bunnycdn.com';
	}

	private function zone(): string {
		return trim( (string) $this->settings->get( 'bunny_storage_zone' ) );
	}

	private function object_url( string $object_name ): string {
		$key = S3_Signer::encode_key( ltrim( $object_name, '/' ) );
		return 'https://' . $this->host() . '/' . rawurlencode( $this->zone() ) . '/' . $key;
	}

	public function upload_file( string $local_path, string $object_name, string $mime = '' ): array {
		if ( ! file_exists( $local_path ) ) {
			throw new \RuntimeException( 'Local file missing: ' . $local_path );
		}
		$size    = (int) filesize( $local_path );
		$headers = [
			'Content-Type' => $mime !== '' ? $mime : 'application/octet-stream',
			'Checksum'     => strtoupper( hash_file( 'sha256', $local_path ) ),
		];
		$resp = $this->limiter->wrap(
			fn() => $this->request( 'PUT', $this->object_url( $object_name ), $headers, $local_path ),
			'upload:' . $object_name
		);
		if ( $resp['status'] < 200 || $resp['status'] >= 300 ) {
			throw new \RuntimeException(
				sprintf( 'Upload failed (%d): %s', $resp['status'], substr( trim( $resp['body'] ), 0, 200 ) ),
				$resp['status']
			);
		}
		return [ 'name' => $object_name, 'size' => $size ];
	}

	public function head_object( string $object_name ): ?array {
		try {
			$key  = ltrim( $object_name, '/' );
			$dir  = ltrim( (string) dirname( $key ), '/' );
			$dir  = $dir === '.' ? '' : $dir;
			$base = basename( $key );

			$list_url = 'https://' . $this->host() . '/' . rawurlencode( $this->zone() ) . '/'
				. ( $dir !== '' ? S3_Signer::encode_key( $dir ) . '/' : '' );

			$resp = $this->limiter->wrap(
				fn() => $this->request( 'GET', $list_url, [] ),
				'head:' . $object_name
			);
			if ( $resp['status'] < 200 || $resp['status'] >= 300 ) {
				return null;
			}
			$entries = json_decode( $resp['body'], true );
			if ( ! is_array( $entries ) ) {
				return null;
			}
			foreach ( $entries as $entry ) {
				if ( ( $entry['ObjectName'] ?? '' ) === $base && empty( $entry['IsDirectory'] ) ) {
					return [
						'size' => (int) ( $entry['Length'] ?? 0 ),
						'md5'  => (string) ( $entry['Checksum'] ?? '' ),
						'etag' => (string) ( $entry['Checksum'] ?? '' ),
					];
				}
			}
			return null;
		} catch ( \Throwable $e ) {
			$this->logger->warn( 'Head failed: ' . $e->getMessage(), 'bunny', [ 'object' => $object_name ] );
			return null;
		}
	}

	public function download_object( string $object_name, string $target_path ): bool {
		try {
			$resp = $this->limiter->wrap(
				fn() => $this->request( 'GET', $this->object_url( $object_name ), [], null, $target_path ),
				'download:' . $object_name
			);
			return $resp['status'] >= 200 && $resp['status'] < 300 && file_exists( $target_path );
		} catch ( \Throwable $e ) {
			$this->logger->warn( 'Download failed: ' . $e->getMessage(), 'bunny', [ 'object' => $object_name ] );
			return false;
		}
	}

	public function delete_object( string $object_name ): void {
		try {
			$this->limiter->wrap(
				fn() => $this->request( 'DELETE', $this->object_url( $object_name ), [] ),
				'delete:' . $object_name
			);
		} catch ( \Throwable $e ) {
			$this->logger->warn( 'Delete failed: ' . $e->getMessage(), 'bunny', [ 'object' => $object_name ] );
		}
	}

	public function test_connection(): array {
		if ( ! $this->is_available() ) {
			return [ 'ok' => false, 'message' => 'Missing storage zone / access key.' ];
		}
		try {
			$url  = 'https://' . $this->host() . '/' . rawurlencode( $this->zone() ) . '/';
			$resp = $this->limiter->wrap( fn() => $this->request( 'GET', $url, [] ), 'test' );
			if ( $resp['status'] >= 200 && $resp['status'] < 300 ) {
				return [ 'ok' => true, 'message' => 'Storage zone reachable (Bunny.net).' ];
			}
			if ( $resp['status'] === 401 ) {
				return [ 'ok' => false, 'message' => 'Unauthorized — check the Access Key (storage zone password).' ];
			}
			return [ 'ok' => false, 'message' => 'HTTP ' . $resp['status'] . ': ' . substr( trim( $resp['body'] ), 0, 200 ) ];
		} catch ( \Throwable $e ) {
			return [ 'ok' => false, 'message' => $e->getMessage() ];
		}
	}

	/**
	 * @return array{status:int,headers:array<string,string>,body:string}
	 */
	private function request( string $method, string $url, array $headers, ?string $upload_file = null, ?string $stream_to = null ): array {
		$headers['AccessKey'] = (string) $this->settings->get( 'secret_key' );
		$headers['Accept']    = 'application/json';

		$args = [
			'method'      => $method,
			'headers'     => $headers,
			'timeout'     => 120,
			'redirection' => 0,
			'sslverify'   => true,
		];
		if ( $upload_file !== null ) {
			$args['body'] = file_get_contents( $upload_file );
		}
		if ( $stream_to !== null ) {
			$args['stream']   = true;
			$args['filename'] = $stream_to;
		}

		$resp = wp_remote_request( $url, $args );
		if ( is_wp_error( $resp ) ) {
			throw new \RuntimeException( 'Transport error: ' . $resp->get_error_message(), 0 );
		}
		$status = (int) wp_remote_retrieve_response_code( $resp );
		if ( $status === 408 || $status === 429 || $status >= 500 ) {
			throw new \RuntimeException( 'Transient HTTP ' . $status, $status );
		}
		return [
			'status'  => $status,
			'headers' => self::headers_array( $resp ),
			'body'    => $stream_to !== null ? '' : (string) wp_remote_retrieve_body( $resp ),
		];
	}

	/** wp_remote_retrieve_headers() may return a dictionary object OR a plain array. */
	private static function headers_array( $resp ): array {
		$h = wp_remote_retrieve_headers( $resp );
		if ( is_object( $h ) && method_exists( $h, 'getAll' ) ) {
			$h = $h->getAll();
		}
		return array_change_key_case( (array) $h, CASE_LOWER );
	}
}
