<?php
/**
 * Plugin Name: OpsGate WP
 * Description: OpsGate connects WordPress sites to the OpsGate platform for evidence-backed site triage, reports, alerts, and operational visibility.
 * Version: 0.10.8
 * Author: OpsGate
 */

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

if ( ! defined( 'OPSGATE_VERSION' ) ) {
    define( 'OPSGATE_VERSION', '0.10.8' );
}

if ( ! defined( 'OPSGATE_PLUGIN_FILE' ) ) {
    define( 'OPSGATE_PLUGIN_FILE', __FILE__ );
}

if ( ! defined( 'OPSGATE_PLUGIN_BASENAME' ) ) {
    define( 'OPSGATE_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
}

// Option keys for structured settings.
define( 'OPSGATE_OPTION_SERVICES',  'opsgate_services' );
define( 'OPSGATE_OPTION_SCHEDULES', 'opsgate_schedules' );
define( 'OPSGATE_OPTION_CONNECTOR_PAIRING_TOKEN', 'opsgate_connector_pairing_token' );
define( 'OPSGATE_OPTION_CONNECTOR_SITE_CREDENTIAL', 'opsgate_connector_site_credential' );
define( 'OPSGATE_OPTION_SITE_ID', 'opsgate_active_site_id' );
define( 'OPSGATE_OPTION_ACCOUNT_ID', 'opsgate_account_id' );

function opsgate_connector_secret_key() {
    $salt = wp_salt( 'auth' );
    return is_string( $salt ) ? hash( 'sha256', $salt, true ) : '';
}

function opsgate_encrypt_connector_secret( $secret ) {
    $secret = is_string( $secret ) ? trim( $secret ) : '';
    if ( '' === $secret ) {
        return '';
    }
    if ( ! function_exists( 'openssl_encrypt' ) ) {
        return base64_encode( $secret );
    }
    $key = opsgate_connector_secret_key();
    if ( '' === $key ) {
        return '';
    }
    $iv = random_bytes( 16 );
    $ciphertext = openssl_encrypt( $secret, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv );
    if ( false === $ciphertext ) {
        return '';
    }
    return base64_encode( $iv . $ciphertext );
}

function opsgate_decrypt_connector_secret( $encoded ) {
    $encoded = is_string( $encoded ) ? trim( $encoded ) : '';
    if ( '' === $encoded ) {
        return '';
    }
    $decoded = base64_decode( $encoded, true );
    if ( false === $decoded ) {
        return '';
    }
    if ( ! function_exists( 'openssl_decrypt' ) ) {
        return is_string( $decoded ) ? $decoded : '';
    }
    $key = opsgate_connector_secret_key();
    if ( '' === $key || ! is_string( $decoded ) || strlen( $decoded ) < 17 ) {
        return '';
    }
    $iv = substr( $decoded, 0, 16 );
    $ciphertext = substr( $decoded, 16 );
    $secret = openssl_decrypt( $ciphertext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv );
    return is_string( $secret ) ? trim( $secret ) : '';
}

/**
 * Default service settings (per-service, per-plan).
 *
 * Keep this in sync with docs/services.md and docs/settings-architecture.md.
 */
function opsgate_default_services_settings() {
    return [
        'wordpress_security_audit' => [
            'enabled'       => false,
            'plan'          => 'free',
            'last_scan_id'  => null,
            'last_scan_at'  => null,
            'last_output'   => [
                'json' => null,
                'csv'  => null,
                'pdf'  => null,
            ],
        ],
    ];
}

/**
 * Default schedule settings (per-service).
 *
 * Keep this in sync with docs/settings-architecture.md.
 */
function opsgate_default_schedules_settings() {
    return [
        'wordpress_security_audit' => [
            'mode'         => 'off',   // off|daily|weekly|monthly
            'time'         => '02:00', // HH:MM in site timezone
            'dow'          => null,    // 0-6 (for weekly)
            'dom'          => null,    // 1-31 (for monthly)
            'last_run_at'  => null,
            'last_scan_id' => null,
        ],
    ];
}

/**
 * Get merged service settings: defaults + stored.
 *
 * @return array
 */
function opsgate_get_services_settings() {
    $defaults = opsgate_default_services_settings();
    $stored   = get_option( OPSGATE_OPTION_SERVICES, [] );

    if ( ! is_array( $stored ) ) {
        $stored = [];
    }

    // Merge shallow: stored overrides defaults per service key.
    return array_merge( $defaults, $stored );
}

/**
 * Persist service settings.
 *
 * @param array $value
 * @return void
 */
function opsgate_update_services_settings( $value ) {
    if ( ! is_array( $value ) ) {
        return;
    }
    update_option( OPSGATE_OPTION_SERVICES, $value );
}

/**
 * Get merged schedule settings: defaults + stored.
 *
 * @return array
 */
function opsgate_get_schedules_settings() {
    $defaults = opsgate_default_schedules_settings();
    $stored   = get_option( OPSGATE_OPTION_SCHEDULES, [] );

    if ( ! is_array( $stored ) ) {
        $stored = [];
    }

    return array_merge( $defaults, $stored );
}

/**
 * Persist schedule settings.
 *
 * @param array $value
 * @return void
 */
function opsgate_update_schedules_settings( $value ) {
    if ( ! is_array( $value ) ) {
        return;
    }
    update_option( OPSGATE_OPTION_SCHEDULES, $value );
}

// === MCP integration constants and helpers ==================================

// Reuse the existing inbound token option for MCP auth.
if ( ! defined( 'OPSGATE_OPTION_API_TOKEN' ) ) {
    define( 'OPSGATE_OPTION_API_TOKEN', 'opsgate_inbound_token' );
}

/**
 * Resolve the MCP API token for OpsGate.
 *
 * Order of precedence:
 *  1) OPSGATE_API_TOKEN constant (e.g. defined in wp-config.php)
 *  2) OPSEGATE_API_TOKEN env var inside the container
 *  3) 'opsgate_inbound_token' option (configured on the Overview tab)
 *
 * @return string|null
 */
function opsgate_get_api_token() {
    $token = null;

    if ( defined( 'OPSGATE_API_TOKEN' ) && OPSGATE_API_TOKEN ) {
        $token = OPSGATE_API_TOKEN;
    } elseif ( getenv( 'OPSGATE_API_TOKEN' ) ) {
        $token = getenv( 'OPSGATE_API_TOKEN' );
    } else {
        $stored = get_option( OPSGATE_OPTION_API_TOKEN );
        if ( is_string( $stored ) && $stored !== '' ) {
            $token = $stored;
        }
    }

    /**
     * Filter the OpsGate MCP API token before it is used.
     *
     * @param string|null $token
     */
    $token = apply_filters( 'opsgate_api_token', $token );

    if ( is_string( $token ) ) {
        $token = trim( $token );
    } else {
        $token = null;
    }

    return $token ?: null;
}

function opsgate_get_update_metadata_url() {
    if ( defined( 'OPSGATE_MCP_BASE_URL' ) && '' !== trim( (string) OPSGATE_MCP_BASE_URL ) ) {
        $base = trim( (string) OPSGATE_MCP_BASE_URL );
    } else {
        $stored = trim( (string) get_option( 'opsgate_api_base_url', '' ) );
        $base   = '' !== $stored ? $stored : 'https://mcp.opsgate.ca';
    }
    return rtrim( $base, '/' ) . '/v1/plugin-updates/opsgate/metadata.json';
}

function opsgate_init_plugin_update_checker() {
    static $initialized = false;
    if ( $initialized ) {
        return;
    }
    $initialized = true;

    $metadata_url = opsgate_get_update_metadata_url();
    if ( '' === trim( (string) $metadata_url ) ) {
        return;
    }

    $library = __DIR__ . '/lib/plugin-update-checker/plugin-update-checker.php';
    if ( ! file_exists( $library ) ) {
        return;
    }

    require_once $library;

    if ( ! class_exists( '\\YahnisElsts\\PluginUpdateChecker\\v5p6\\PucFactory' ) ) {
        return;
    }

    $checker = \YahnisElsts\PluginUpdateChecker\v5p6\PucFactory::buildUpdateChecker(
        $metadata_url,
        OPSGATE_PLUGIN_FILE,
        'opsgate'
    );
    $GLOBALS['opsgate_update_checker'] = $checker;
}

// Load classes.
require_once __DIR__ . '/includes/class-opsgate-admin.php';
require_once __DIR__ . '/includes/class-opsgate-rest.php';
require_once __DIR__ . '/includes/class-opsgate-logger.php';

/**
 * Activation: seed base options (but do not overwrite existing installs).
 */
register_activation_hook(
    __FILE__,
    function () {
        // Existing telemetry + badges.
        if ( get_option( 'opsgate_last_scan', null ) === null ) {
            add_option( 'opsgate_last_scan', '' );
        }
        if ( get_option( 'opsgate_last_backup', null ) === null ) {
            add_option( 'opsgate_last_backup', '' );
        }
        if ( get_option( 'opsgate_ssl_days_remaining', null ) === null ) {
            add_option( 'opsgate_ssl_days_remaining', '' );
        }
        if ( get_option( 'opsgate_telemetry_enabled', null ) === null ) {
            add_option( 'opsgate_telemetry_enabled', '0' );
        }
        if ( get_option( 'opsgate_telemetry_counts', null ) === null ) {
            add_option(
                'opsgate_telemetry_counts',
                [
                    'scans_triggered'   => 0,
                    'reports_generated' => 0,
                    'applies_executed'  => 0,
                ]
            );
        }
        if ( get_option( 'opsgate_inbound_token', null ) === null ) {
            // Set in Settings (matches n8n / MCP X-Token).
            add_option( 'opsgate_inbound_token', '' );
        }

        // New structured settings (only seed if not present).
        if ( get_option( OPSGATE_OPTION_SERVICES, null ) === null ) {
            add_option( OPSGATE_OPTION_SERVICES, opsgate_default_services_settings() );
        }
        if ( get_option( OPSGATE_OPTION_SCHEDULES, null ) === null ) {
            add_option( OPSGATE_OPTION_SCHEDULES, opsgate_default_schedules_settings() );
        }
        if ( get_option( OPSGATE_OPTION_CONNECTOR_PAIRING_TOKEN, null ) === null ) {
            add_option( OPSGATE_OPTION_CONNECTOR_PAIRING_TOKEN, '' );
        }
        if ( get_option( OPSGATE_OPTION_CONNECTOR_SITE_CREDENTIAL, null ) === null ) {
            add_option( OPSGATE_OPTION_CONNECTOR_SITE_CREDENTIAL, '' );
        }

	// Day 19.4 (Option B): Scheduling is available but OFF by default.
	// WP.org compliance: do not schedule background jobs automatically.
	// When the admin explicitly enables scheduling, the Schedules screen will provision cron safely.
    }
);

register_deactivation_hook(
    __FILE__,
    function () {
        $hook = 'opsgate_scheduled_tick';
        wp_clear_scheduled_hook( $hook );
    }
);

/**
 * Bootstrap plugin on load.
 */
add_action(
    'plugins_loaded',
    function () {
        opsgate_init_plugin_update_checker();
        OpsGate_Logger::init();
        OpsGate_Admin::init();
        OpsGate_REST::init();
    }
);
