<?php

declare(strict_types=1);

namespace SecurWP\Includes;

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

/**
 * Self-hosted plugin update checker.
 *
 * Checks securewp.net for newer versions and serves them through the
 * standard WordPress plugin update flow. This file is EXCLUDED from
 * the WP.org distribution by the build script; sites that install
 * from wordpress.org receive updates through the WP.org repository.
 */
class SelfUpdater {

    /** Remote zip URL. */
    private const PACKAGE_URL = 'https://securewp.net/securwp.zip';

    /** Public product page shown as the plugin homepage in wp-admin. */
    private const HOMEPAGE_URL = 'https://securewp.net/wordpress-security-plugin/';

    /** Public author website shown in the plugin details modal. */
    private const AUTHOR_URL = 'https://securewp.net';

    /** Remote metadata endpoint (JSON). */
    private const METADATA_URL = 'https://securewp.net/securwp-update.json';

    /** Transient cache key for the remote version check. */
    private const CACHE_KEY = 'securwp_self_update_check';

    /** Cache lifetime in seconds (12 hours). */
    private const CACHE_TTL = 12 * HOUR_IN_SECONDS;

    /** Plugin basename (e.g. securwp/securwp.php). */
    private string $basename;

    public function __construct() {
        $this->basename = plugin_basename( SECURWP_PLUGIN_FILE );
    }

    /**
     * Register WordPress hooks.
     */
    public function init(): void {
        add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'check_update' ] );
        add_filter( 'plugins_api', [ $this, 'plugin_info' ], 10, 3 );

        // Clear cache when the user forces a check via Dashboard > Updates.
        add_action( 'load-update-core.php', [ $this, 'clear_cache' ] );
    }

    /**
     * Inject update data into the WordPress update transient.
     *
     * @param object $transient The update_plugins transient value.
     * @return object Modified transient.
     */
    public function check_update( $transient ) {
        if ( ! is_object( $transient ) ) {
            $transient = new \stdClass();
        }

        $remote = $this->get_remote_metadata();
        if ( $remote === null ) {
            return $transient;
        }

        if ( version_compare( SECURWP_VERSION, $remote['version'], '<' ) ) {
            $transient->response[ $this->basename ] = (object) [
                'slug'         => 'securwp',
                'plugin'       => $this->basename,
                'new_version'  => $remote['version'],
                'url'          => self::HOMEPAGE_URL,
                'package'      => $remote['download_url'] ?? self::PACKAGE_URL,
                'tested'       => $remote['tested'] ?? '',
                'requires'     => $remote['requires'] ?? '6.0',
                'requires_php' => $remote['requires_php'] ?? '7.4',
            ];
        } else {
            // Tell WordPress we checked and there is no update.
            $transient->no_update[ $this->basename ] = (object) [
                'slug'        => 'securwp',
                'plugin'      => $this->basename,
                'new_version' => SECURWP_VERSION,
                'url'         => self::HOMEPAGE_URL,
            ];
        }

        return $transient;
    }

    /**
     * Provide plugin info for the "View details" modal in wp-admin.
     *
     * @param false|object|array $result Default result.
     * @param string             $action API action.
     * @param object             $args   Request arguments.
     * @return false|object
     */
    public function plugin_info( $result, $action, $args ) {
        if ( $action !== 'plugin_information' ) {
            return $result;
        }

        if ( ! isset( $args->slug ) || $args->slug !== 'securwp' ) {
            return $result;
        }

        $remote = $this->get_remote_metadata();
        if ( $remote === null ) {
            return $result;
        }

        return (object) [
            'name'          => $remote['name'] ?? 'SecurWP',
            'slug'          => 'securwp',
            'version'       => $remote['version'],
            'author'        => sprintf( '<a href="%s">SecurWP Team</a>', esc_url( self::AUTHOR_URL ) ),
            'homepage'      => self::HOMEPAGE_URL,
            'download_link' => $remote['download_url'] ?? self::PACKAGE_URL,
            'tested'        => $remote['tested'] ?? '',
            'requires'      => $remote['requires'] ?? '6.0',
            'requires_php'  => $remote['requires_php'] ?? '7.4',
            'sections'      => [
                'description' => $remote['description'] ?? 'Professional Security, Advanced Firewall, and Deep Malware Scanning for WordPress.',
                'changelog'   => $remote['changelog'] ?? '',
            ],
        ];
    }

    /**
     * Clear the cached remote metadata.
     */
    public function clear_cache(): void {
        delete_transient( self::CACHE_KEY );
    }

    /**
     * Fetch and cache remote update metadata.
     *
     * Expected JSON format from METADATA_URL:
     * {
     *   "version": "1.0.64",
     *   "download_url": "https://securewp.net/securwp.zip",
     *   "homepage": "https://securewp.net/wordpress-security-plugin/",
     *   "requires": "6.0",
     *   "requires_php": "7.4",
     *   "tested": "6.8",
     *   "name": "SecurWP",
     *   "description": "...",
     *   "changelog": "<h4>1.0.64</h4><ul><li>...</li></ul>"
     * }
     *
     * @return array|null Parsed metadata or null on failure.
     */
    private function get_remote_metadata(): ?array {
        $cached = get_transient( self::CACHE_KEY );
        if ( is_array( $cached ) ) {
            return $this->normalize_remote_metadata( $cached );
        }

        $response = wp_remote_get(
            self::METADATA_URL,
            [
                'timeout' => 10,
                'headers' => [ 'Accept' => 'application/json' ],
            ]
        );

        if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
            // Cache the failure briefly (30 min) to avoid hammering the server.
            set_transient( self::CACHE_KEY, [ '_failed' => true ], 30 * MINUTE_IN_SECONDS );
            return null;
        }

        $body = wp_remote_retrieve_body( $response );
        $data = json_decode( $body, true );

        if ( ! is_array( $data ) ) {
            return null;
        }

        $metadata = $this->normalize_remote_metadata( $data );
        if ( $metadata === null ) {
            return null;
        }

        set_transient( self::CACHE_KEY, $metadata, self::CACHE_TTL );

        return $metadata;
    }

    /**
     * Validate and normalize remote updater metadata before using it.
     *
     * @param array $data Raw decoded JSON or cached transient payload.
     * @return array|null Normalized metadata, or null when required fields are missing.
     */
    private function normalize_remote_metadata( array $data ): ?array {
        $version = isset( $data['version'] ) && is_scalar( $data['version'] ) ? trim( (string) $data['version'] ) : '';
        if ( $version === '' ) {
            return null;
        }

        $data['version'] = $version;

        return $data;
    }
}
