<?php
/**
 * Registration Handler for StaticQ
 *
 * Handles registration of existing WordPress attachments.
 * Registration = Database registration only, NO file operations or existence checks.
 * File existence is verified later by Health Check.
 *
 * Extends SQMedia_Batch_Processor for shared lifecycle management (start/process/cancel/pause/resume).
 *
 * @package suspended-cloud/cloudflare-media-offload
 * @since 3.0.0
 */

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

class SQMedia_Registration_Handler extends SQMedia_Batch_Processor {

    protected $scan_type = 'registration';
    protected $cron_hook = 'sqmedia_continue_registration';
    protected $batch_size = 500;

    // =========================================================================
    // BATCH PROCESSOR ABSTRACT IMPLEMENTATIONS
    // =========================================================================

    /**
     * Initialize batch size from config
     * Registration uses SQMEDIA_REGISTRATION_BATCH_SIZE, clamped 50-2000
     */
    protected function init_batch_size(): void {
        if (defined('SQMEDIA_REGISTRATION_BATCH_SIZE')) {
            $this->batch_size = (int) SQMEDIA_REGISTRATION_BATCH_SIZE;
        }
        $this->batch_size = max(50, min(2000, $this->batch_size));
    }

    /**
     * Get all unregistered attachment IDs
     */
    protected function get_items_to_process(array $options = []): array {
        return $this->db->get_unregistered_attachment_ids($options);
    }

    /**
     * Process a single attachment: register it in the database
     */
    protected function process_single_item(int $item_id, array $context): array {
        $result = $this->register_attachment($item_id, $context['scan_id']);

        if ($result['success']) {
            return [
                'success' => true,
                'issues' => [],
                'title' => get_the_title($item_id),
            ];
        }

        if (!empty($result['skipped'])) {
            return [
                'success' => true,
                'skipped' => true,
                'issues' => [],
                'reason' => $result['reason'] ?? __('Unknown reason', 'staticq-media'),
                'file' => get_post_meta($item_id, '_wp_attached_file', true) ?: '',
            ];
        }

        return [
            'success' => false,
            'issues' => [['category' => 'registration_error', 'message' => $result['error'] ?? __('Unknown error', 'staticq-media')]],
            'error' => $result['error'] ?? __('Unknown error', 'staticq-media'),
            'file' => get_post_meta($item_id, '_wp_attached_file', true) ?: '',
        ];
    }

    /**
     * Build processing context
     */
    protected function build_context(string $scan_id): array {
        return ['scan_id' => $scan_id];
    }

    /**
     * Handle scan completion
     */
    protected function on_complete(string $scan_id, array $results): void {
        $reg = $results['registration'] ?? [];
        sqmedia()->log('Registration completed', 'info', [
            'scan_id' => $scan_id,
            'registered' => $reg['registered'] ?? 0,
            'skipped' => $reg['skipped'] ?? 0,
            'errors' => $reg['errors'] ?? 0,
        ]);
    }

    /**
     * Get human-readable label
     */
    public function get_label(): string {
        return __('Registration', 'staticq-media');
    }

    // =========================================================================
    // OPTIONAL HOOKS
    // =========================================================================

    /**
     * After each batch: track registration-specific counters and recent items
     */
    protected function on_batch_complete(string $scan_id, array $batch_results, array $cumulative_results): void {
        $scan = $this->db->get_scan($scan_id);
        if (!$scan) {
            return;
        }

        $results = $scan->results;

        // Initialize registration counters
        if (!isset($results['registration'])) {
            $results['registration'] = [
                'registered' => 0,
                'skipped' => 0,
                'errors' => 0,
                'error_details' => [],
                'skipped_details' => [],
                'recent' => [],
            ];
        }

        $reg = &$results['registration'];

        foreach ($batch_results as $item_id => $item_result) {
            if (!empty($item_result['skipped'])) {
                $reg['skipped']++;
                $reg['skipped_details'][] = [
                    'attachment_id' => $item_id,
                    'file' => $item_result['file'] ?? '',
                    'reason' => $item_result['reason'] ?? __('Unknown reason', 'staticq-media'),
                ];
            } elseif (!empty($item_result['issues'])) {
                $reg['errors']++;
                $reg['error_details'][] = [
                    'attachment_id' => $item_id,
                    'file' => $item_result['file'] ?? '',
                    'error' => $item_result['error'] ?? __('Unknown error', 'staticq-media'),
                ];
            } else {
                $reg['registered']++;
                $reg['recent'][] = [
                    'id' => $item_id,
                    'title' => $item_result['title'] ?? get_the_title($item_id),
                ];
            }
        }

        // Keep only last 5 recent items
        $reg['recent'] = array_slice($reg['recent'], -5);

        $this->db->update_scan($scan_id, [
            'results' => $results,
        ]);
    }

    /**
     * On cancel: delete all records created during this registration
     */
    protected function on_cancel(string $scan_id): void {
        $deleted = $this->db->delete_by_batch($scan_id);

        sqmedia()->log('Registration cancelled', 'info', [
            'scan_id' => $scan_id,
            'deleted_records' => $deleted,
        ]);
    }

    /**
     * Recover item IDs if transient was lost
     * Rebuilds from current unregistered list + stored filters
     */
    protected function recover_item_ids(string $scan_id, object $scan): ?array {
        $results = $scan->results ?? [];
        $filters = [];

        if (!empty($results['filters']['start_date'])) {
            $filters['start_date'] = $results['filters']['start_date'];
        }
        if (!empty($results['filters']['end_date'])) {
            $filters['end_date'] = $results['filters']['end_date'];
        }

        $ids = $this->db->get_unregistered_attachment_ids($filters);

        if (empty($ids)) {
            return null;
        }

        $this->log('Recovered item IDs from database', ['count' => count($ids), 'scan_id' => $scan_id]);
        return $ids;
    }

    // =========================================================================
    // START OVERRIDE - Handle zero-items as error + store filters
    // =========================================================================

    /**
     * Start registration
     * Overrides parent to return success:false when no items (matching old behavior)
     * and to store filters in results for recovery
     */
    public function start(array $options = []): array {
        // Check for existing active scan
        $active = $this->db->get_active_scan($this->scan_type);
        if ($active) {
            return [
                'success' => false,
                'error' => __('A registration is already in progress. Please complete or cancel it first.', 'staticq-media'),
                'scan_id' => $active->scan_id,
                'batch_id' => $active->scan_id,
            ];
        }

        // Pre-check: if no items, return error without creating scan record
        $item_ids = $this->get_items_to_process($options);
        if (empty($item_ids)) {
            return [
                'success' => false,
                'error' => __('No attachments available for registration. All image attachments are already registered.', 'staticq-media'),
            ];
        }

        // Create scan record
        $scan_id = $this->db->create_scan($this->scan_type);
        if (!$scan_id) {
            return [
                'success' => false,
                'error' => __('Failed to create scan record.', 'staticq-media'),
            ];
        }

        $total = count($item_ids);

        // Store item IDs in transient
        set_transient('sqmedia_scan_ids_' . $scan_id, $item_ids, HOUR_IN_SECONDS * 4);

        // Build initial results with filters and registration counters
        $initial_results = [
            'filters' => [
                'start_date' => $options['start_date'] ?? null,
                'end_date' => $options['end_date'] ?? null,
            ],
            'summary' => [
                'total' => $total,
                'processed' => 0,
                'clean' => 0,
                'with_issues' => 0,
            ],
            'by_category' => [],
            'issues' => [],
            'registration' => [
                'registered' => 0,
                'skipped' => 0,
                'errors' => 0,
                'error_details' => [],
                'skipped_details' => [],
                'recent' => [],
            ],
        ];

        // Update scan record
        $this->db->update_scan($scan_id, [
            'status' => 'running',
            'total_items' => $total,
            'processed_items' => 0,
            'scan_started' => current_time('mysql'),
            'results' => $initial_results,
        ]);

        // Schedule cron
        $this->schedule_cron();

        sqmedia()->log('Registration started', 'info', [
            'scan_id' => $scan_id,
            'total' => $total,
            'filters' => $options,
        ]);

        return [
            'success' => true,
            'scan_id' => $scan_id,
            'batch_id' => $scan_id,
            'status' => 'running',
            'total' => $total,
            'processed' => 0,
        ];
    }

    // =========================================================================
    // PUBLIC UTILITY METHODS (not part of batch lifecycle)
    // =========================================================================

    /**
     * Count unregistered attachments
     */
    public function count_unregistered(array $filters = []): int {
        return $this->db->count_unregistered_attachments($filters);
    }

    /**
     * Register a single attachment without a batch (for testing/single-item use)
     */
    public function register_single_attachment(int $attachment_id): array {
        return $this->register_attachment($attachment_id, null);
    }

    /**
     * Get library overview stats
     */
    public function get_library_stats(): array {
        global $wpdb;

        // Total image attachments
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $total = (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$wpdb->posts}
             WHERE post_type = 'attachment'
             AND post_mime_type LIKE 'image/%'"
        );

        // Already registered
        $registered = $this->db->get_total_record_count();

        // Available for registration
        $available = max(0, $total - $registered);

        return [
            'total_attachments' => $total,
            'registered' => $registered,
            'available' => $available,
            'percentage' => $total > 0 ? round(($registered / $total) * 100, 1) : 0,
        ];
    }

    /**
     * Get registration history from both legacy (sq_migration_batches) and new (sq_scan_results) tables
     */
    public function get_history(int $limit = 10): array {
        // Get new scan-based history
        $scan_history = $this->db->get_scan_history($this->scan_type, $limit);

        // Get legacy batch history
        $batch_history = $this->db->get_batch_history($limit);

        // Normalize scan history to match legacy format
        $normalized = [];

        foreach ($scan_history as $scan) {
            $reg = $scan->results['registration'] ?? [];
            $normalized[] = (object) [
                'batch_id' => $scan->scan_id,
                'status' => $scan->status,
                'total_attachments' => (int) $scan->total_items,
                'processed_attachments' => (int) $scan->processed_items,
                'registered_attachments' => $reg['registered'] ?? 0,
                'skipped_attachments' => $reg['skipped'] ?? 0,
                'error_attachments' => $reg['errors'] ?? 0,
                'results' => $scan->results,
                'started_at' => $scan->scan_started,
                'completed_at' => $scan->scan_completed,
                'cancelled_at' => $scan->status === 'cancelled' ? $scan->scan_completed : null,
                'created_at' => $scan->scan_started,
                'source' => 'scan',
            ];
        }

        // Mark legacy entries
        foreach ($batch_history as $batch) {
            $batch->source = 'legacy';
        }

        // Merge and sort by date descending
        $all = array_merge($normalized, $batch_history);
        usort($all, function ($a, $b) {
            $date_a = $a->started_at ?? $a->created_at ?? '1970-01-01';
            $date_b = $b->started_at ?? $b->created_at ?? '1970-01-01';
            return strcmp($date_b, $date_a);
        });

        return array_slice($all, 0, $limit);
    }

    /**
     * Get active registration in legacy-compatible format
     * Returns object matching old $active_batch shape for UI rendering
     */
    public function get_active_registration_compat(): ?object {
        // Check new system first
        $active_scan = $this->db->get_active_scan($this->scan_type);

        if ($active_scan && in_array($active_scan->status, ['pending', 'running', 'paused'], true)) {
            $reg = $active_scan->results['registration'] ?? [];
            return (object) [
                'batch_id' => $active_scan->scan_id,
                'status' => $active_scan->status,
                'total_attachments' => (int) $active_scan->total_items,
                'processed_attachments' => (int) $active_scan->processed_items,
                'registered_attachments' => $reg['registered'] ?? 0,
                'skipped_attachments' => $reg['skipped'] ?? 0,
                'error_attachments' => $reg['errors'] ?? 0,
                'results' => $active_scan->results,
                'started_at' => $active_scan->scan_started,
                'created_at' => $active_scan->scan_started,
            ];
        }

        // Fall back to legacy system
        return $this->db->get_active_batch();
    }

    // =========================================================================
    // INTERNAL: Register a single attachment
    // =========================================================================

    /**
     * Register a single attachment
     *
     * FAST REGISTRATION: Only reads WordPress metadata, no disk/bucket checks.
     * File existence will be verified by Health Check scan.
     *
     * Simplified schema stores only:
     * - Path information (master_path, master_dir, master_filename)
     * - original_image (if WordPress scaled on upload)
     * - storage_location = 'local' (default, Health Check will update)
     * - has_webp = 0 (Health Check Phase 2 will verify and update)
     */
    public function register_attachment(int $attachment_id, ?string $batch_id = null): array {
        // Check if already registered
        if ($this->db->is_registered($attachment_id)) {
            return [
                'success' => false,
                'skipped' => true,
                'reason' => __('Already registered', 'staticq-media'),
            ];
        }

        // Get WordPress metadata
        // IMPORTANT: Use direct DB query to bypass WP Offload Media filters
        global $wpdb;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $raw_metadata = $wpdb->get_var($wpdb->prepare(
            "SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = '_wp_attachment_metadata'",
            $attachment_id
        ));

        $metadata = [];
        if ($raw_metadata) {
            $metadata = maybe_unserialize($raw_metadata);

            // If unserialize failed, try to repair string lengths
            if (!is_array($metadata)) {
                $fixed = preg_replace_callback(
                    '/s:(\d+):"(.*?)";/s',
                    function($m) { return 's:' . strlen($m[2]) . ':"' . $m[2] . '";'; },
                    $raw_metadata
                );
                $metadata = @unserialize($fixed);
            }

            if (!is_array($metadata)) {
                // maybe_unserialize might return string if double-serialized
                if (is_string($metadata)) {
                    $metadata = maybe_unserialize($metadata);
                }
                if (!is_array($metadata)) {
                    $metadata = [];
                }
            }
        }

        // Get file path directly from database to avoid WP Offload Media hijacking
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $file = $wpdb->get_var($wpdb->prepare(
            "SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = '_wp_attached_file'",
            $attachment_id
        ));

        if (!$file) {
            return [
                'success' => false,
                'error' => __('Could not get attachment file path', 'staticq-media'),
            ];
        }

        // If the file path looks like a cloud URL (from WP Offload Media), skip it
        if (preg_match('#^[a-z0-9]+://#i', $file)) {
            return [
                'success' => false,
                'skipped' => true,
                'reason' => __('Corrupted file path in database (cloud URL from another plugin)', 'staticq-media'),
            ];
        }

        // Parse paths - _wp_attached_file stores relative path from uploads dir
        $relative_path = $file;
        $master_dir = dirname($relative_path);
        $master_filename = basename($relative_path);

        if ($master_dir === '.') {
            $master_dir = '';
        }

        // Get original image filename (if WordPress scaled it)
        $original_image = $metadata['original_image'] ?? null;

        // Safeguard: If original_image is not a string, set it to null
        if ($original_image !== null && !is_string($original_image)) {
            $original_image = null;
        }

        // Create record with NEW simplified schema
        // WordPress postmeta is source of truth for sizes/paths
        // Health Check will verify actual file states
        $record_data = [
            'attachment_id' => $attachment_id,
            'storage_location' => 'local',                // Safe default — scanner will align to settings
            'master_path' => $relative_path,
            'master_dir' => $master_dir,
            'master_filename' => $master_filename,
            'original_image' => $original_image,
            // Leave original_location NULL even when an original_image exists.
            // Registration is read-only: it cannot verify where the original
            // actually sits on disk. The current setting only describes intent
            // for new uploads, not the location of historical files. Writing
            // that intent here would mis-claim placement for legacy originals
            // (e.g., the user just enabled `move_to_originals` but old originals
            // are still alongside their masters). The Media Library Scanner
            // promotes NULL to `alongside`/`originals` based on observed state.
            // Until then, the Orphan Scanner's NULL branch indexes BOTH
            // locations so legacy originals are never mis-flagged as orphans.
            'original_location' => null,
            'has_webp' => 0,                      // Unknown - Health Check will verify
            'registered_via' => 'migration',
            'batch_id' => $batch_id,
            'status' => 'completed',              // Completed = registered, won't be processed by cron
        ];

        $id = $this->db->create_record($record_data);

        if (!$id) {
            return [
                'success' => false,
                'error' => __('Failed to create database record', 'staticq-media'),
            ];
        }

        return [
            'success' => true,
            'id' => $id,
            'attachment_id' => $attachment_id,
        ];
    }
}