<?php

namespace CaptureRelay;

final class Activator
{
    public static function activate(): void
    {
        if (version_compare(PHP_VERSION, '8.1', '<')) {
            deactivate_plugins(plugin_basename(CAPTURE_RELAY_FILE));
            wp_die(esc_html__('CaptureRelay requires PHP 8.1 or newer.', 'capture-relay'));
        }
        if (version_compare((string) get_bloginfo('version'), '6.6', '<')) {
            deactivate_plugins(plugin_basename(CAPTURE_RELAY_FILE));
            wp_die(esc_html__('CaptureRelay requires WordPress 6.6 or newer.', 'capture-relay'));
        }
        if (!extension_loaded('openssl')) {
            deactivate_plugins(plugin_basename(CAPTURE_RELAY_FILE));
            wp_die(esc_html__('CaptureRelay requires the PHP OpenSSL extension.', 'capture-relay'));
        }

        $isInitialActivation = get_option('capture_relay_settings', null) === null;
        self::installSchema();
        add_option('capture_relay_settings', Config::defaults());
        if ($isInitialActivation) {
            add_option('capture_relay_needs_setup', true, '', false);
            add_option('capture_relay_activated_at', time(), '', false);
        }
        Cron::schedule();
        flush_rewrite_rules(false);
    }

    public static function maybeUpgrade(): void
    {
        if ((string) get_option('capture_relay_db_version', '') === Config::DB_VERSION) {
            return;
        }
        self::installSchema();
    }

    private static function installSchema(): void
    {
        global $wpdb;
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        $charset = $wpdb->get_charset_collate();

        dbDelta('CREATE TABLE ' . Config::table('requests') . " (
            id bigint unsigned NOT NULL AUTO_INCREMENT,
            request_id varchar(64) NOT NULL,
            secret_hash varchar(255) NOT NULL,
            device_uuid varchar(64) NOT NULL,
            device_name varchar(160) NOT NULL,
            platform varchar(32) NOT NULL,
            os_version varchar(64) NOT NULL DEFAULT '',
            device_model varchar(120) NOT NULL DEFAULT '',
            app_version varchar(32) NOT NULL DEFAULT '',
            requested_capabilities text NULL,
            client_nonce varchar(96) NOT NULL,
            status varchar(24) NOT NULL DEFAULT 'pending',
            ip_hash char(64) NOT NULL,
            ip_address varchar(45) NOT NULL DEFAULT '',
            created_at datetime NOT NULL,
            expires_at datetime NOT NULL,
            decided_at datetime NULL,
            decided_by bigint unsigned NULL,
            device_id varchar(64) NULL,
            credential_envelope longtext NULL,
            envelope_expires_at datetime NULL,
            completed_at datetime NULL,
            identity_hash char(64) NOT NULL DEFAULT '',
            attestation_status varchar(24) NOT NULL DEFAULT 'unverified',
            PRIMARY KEY  (id),
            UNIQUE KEY request_id (request_id),
            KEY device_uuid (device_uuid),
            KEY status_expires (status, expires_at)
        ) $charset;");

        dbDelta('CREATE TABLE ' . Config::table('devices') . " (
            id bigint unsigned NOT NULL AUTO_INCREMENT,
            device_id varchar(64) NOT NULL,
            website_id varchar(64) NOT NULL,
            credential_hash varchar(255) NOT NULL,
            credential_cipher longtext NOT NULL,
            display_name varchar(160) NOT NULL,
            platform varchar(32) NOT NULL,
            device_model varchar(120) NOT NULL DEFAULT '',
            app_version varchar(32) NOT NULL DEFAULT '',
            status varchar(24) NOT NULL DEFAULT 'approved',
            created_at datetime NOT NULL,
            last_request_at datetime NULL,
            last_upload_at datetime NULL,
            revoked_at datetime NULL,
            revoked_by bigint unsigned NULL,
            identity_hash char(64) NOT NULL DEFAULT '',
            PRIMARY KEY  (id),
            UNIQUE KEY device_id (device_id),
            KEY status (status)
        ) $charset;");

        dbDelta('CREATE TABLE ' . Config::table('blocks') . " (
            id bigint unsigned NOT NULL AUTO_INCREMENT,
            identity_hash char(64) NOT NULL,
            identity_type varchar(32) NOT NULL DEFAULT 'legacy_uuid',
            display_name varchar(160) NOT NULL DEFAULT '',
            platform varchar(32) NOT NULL DEFAULT 'unknown',
            reason varchar(255) NOT NULL DEFAULT '',
            source_request_id varchar(64) NULL,
            blocked_at datetime NOT NULL,
            blocked_by bigint unsigned NULL,
            last_seen_at datetime NULL,
            hit_count bigint unsigned NOT NULL DEFAULT 0,
            PRIMARY KEY  (id),
            UNIQUE KEY identity_hash (identity_hash),
            KEY blocked_at (blocked_at)
        ) $charset;");

        dbDelta('CREATE TABLE ' . Config::table('rate_limits') . " (
            bucket_hash char(64) NOT NULL,
            hit_count bigint unsigned NOT NULL DEFAULT 0,
            window_started_at datetime NOT NULL,
            expires_at datetime NOT NULL,
            PRIMARY KEY  (bucket_hash),
            KEY expires_at (expires_at)
        ) $charset;");

        dbDelta('CREATE TABLE ' . Config::table('nonces') . " (
            id bigint unsigned NOT NULL AUTO_INCREMENT,
            principal varchar(80) NOT NULL,
            nonce_hash char(64) NOT NULL,
            created_at datetime NOT NULL,
            expires_at datetime NOT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY principal_nonce (principal, nonce_hash),
            KEY expires_at (expires_at)
        ) $charset;");

        dbDelta('CREATE TABLE ' . Config::table('uploads') . " (
            id bigint unsigned NOT NULL AUTO_INCREMENT,
            client_upload_id varchar(64) NOT NULL,
            device_id varchar(64) NOT NULL,
            attachment_id bigint unsigned NULL,
            image_title varchar(160) NOT NULL,
            photo_credit varchar(160) NOT NULL DEFAULT '',
            status varchar(24) NOT NULL,
            error_code varchar(80) NOT NULL DEFAULT '',
            error_message text NULL,
            capture_at datetime NULL,
            created_at datetime NOT NULL,
            completed_at datetime NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY upload_device (client_upload_id, device_id),
            KEY device_created (device_id, created_at),
            KEY status (status)
        ) $charset;");

        dbDelta('CREATE TABLE ' . Config::table('activity') . " (
            id bigint unsigned NOT NULL AUTO_INCREMENT,
            event_type varchar(80) NOT NULL,
            severity varchar(16) NOT NULL DEFAULT 'info',
            request_id varchar(64) NULL,
            device_id varchar(64) NULL,
            attachment_id bigint unsigned NULL,
            message text NOT NULL,
            context longtext NULL,
            created_at datetime NOT NULL,
            PRIMARY KEY  (id),
            KEY event_created (event_type, created_at),
            KEY device_id (device_id)
        ) $charset;");

        self::migrateSecurityData();
        update_option('capture_relay_db_version', Config::DB_VERSION);
    }

    private static function migrateSecurityData(): void
    {
        global $wpdb;
        $requests = $wpdb->get_results($wpdb->prepare(
            'SELECT id, device_uuid, platform FROM %i',
            Config::table('requests')
        ));
        foreach ($requests as $request) {
            $wpdb->update(
                Config::table('requests'),
                ['identity_hash' => Config::identityHash((string) $request->device_uuid, (string) $request->platform)],
                ['id' => (int) $request->id],
                ['%s'],
                ['%d']
            );
        }

        $wpdb->query($wpdb->prepare(
            "UPDATE %i d INNER JOIN %i r ON r.device_id = d.device_id SET d.identity_hash = r.identity_hash WHERE r.identity_hash <> ''",
            Config::table('devices'),
            Config::table('requests')
        ));
        $wpdb->query($wpdb->prepare(
            "UPDATE %i SET ip_address = '' WHERE status <> 'pending' AND ip_address <> ''",
            Config::table('requests')
        ));
    }

    public static function deactivate(): void
    {
        Cron::unschedule();
        flush_rewrite_rules(false);
    }
}
