<?php

namespace MediaTrim\Core;

use MediaTrim\Traits\Singleton;

defined('ABSPATH') || exit;

/**
 * Class FileSystemScanner
 *
 * Scans the uploads directory for orphaned files not registered in the media library.
 *
 * @package MediaTrim\Core
 */
class FileSystemScanner
{
    use Singleton;

    /**
     * Uploads base directory
     *
     * @var string
     */
    private $uploads_dir;

    /**
     * Orphaned files table name
     *
     * @var string
     */
    private $table_name;

    /**
     * Excluded directories
     *
     * @var array
     */
    private $excluded_dirs = [
        'media-trim-trash',
        'wc-logs',
        'cache',
        'backups',
    ];

    /**
     * Allowed file extensions
     *
     * @var array
     */
    private $allowed_extensions = [
        'jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'ico', 'bmp',
        'mp4', 'mov', 'avi', 'wmv', 'webm', 'mkv',
        'mp3', 'wav', 'ogg', 'flac', 'm4a',
        'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
        'zip', 'tar', 'gz', 'rar',
    ];

    /**
     * Constructor
     */
    public function __construct()
    {
        global $wpdb;
        $this->table_name = $wpdb->prefix . 'mtrim_orphaned_files';

        $upload_dir = wp_upload_dir();
        $this->uploads_dir = $upload_dir['basedir'];

        $this->init_hooks();
    }

    /**
     * Initialize hooks
     */
    public function init_hooks()
    {
        add_action('wp_ajax_mtrim_scan_filesystem', [$this, 'ajax_scan_filesystem']);
        add_action('wp_ajax_mtrim_get_orphaned_files', [$this, 'ajax_get_orphaned_files']);
        add_action('wp_ajax_mtrim_delete_orphaned_file', [$this, 'ajax_delete_orphaned_file']);
        add_action('wp_ajax_mtrim_add_to_media_library', [$this, 'ajax_add_to_media_library']);
    }

    /**
     * AJAX handler to scan filesystem
     */
    public function ajax_scan_filesystem()
    {
        check_ajax_referer('mtrim_admin_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Unauthorized', 'media-trim')]);
        }

        $offset = isset($_POST['offset']) ? absint($_POST['offset']) : 0;
        $batch_size = 100;

        // If starting fresh, clear previous results
        if ($offset === 0) {
            global $wpdb;
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safely constructed.
            $wpdb->query("TRUNCATE TABLE {$this->table_name}");
        }

        $results = $this->scan_batch($offset, $batch_size);

        // Update scan manager state
        $scan_manager = ScanManager::get_instance();
        if ($offset === 0) {
            $scan_manager->save_state('filesystem', 0, $results['total']);
        } else {
            $scan_manager->save_state('filesystem', $results['next_offset'], $results['total']);
        }

        // If complete, log to history
        if ($results['is_complete']) {
            global $wpdb;
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safely constructed.
            $found_orphaned = $wpdb->get_var("SELECT COUNT(*) FROM {$this->table_name}");
            $scan_manager->complete_scan('filesystem', ['found' => $found_orphaned]);
        }

        wp_send_json_success($results);
    }

    /**
     * Scan a batch of files
     *
     * @param int $offset Starting offset.
     * @param int $limit Batch size.
     * @return array
     */
    public function scan_batch($offset, $limit)
    {
        // Get all files in the uploads directory
        $all_files = $this->get_all_files($this->uploads_dir);
        $total_files = count($all_files);

        // Get the batch of files to process
        $batch_files = array_slice($all_files, $offset, $limit);

        $orphaned_count = 0;

        foreach ($batch_files as $file_path) {
            if ($this->is_orphaned($file_path)) {
                $this->save_orphaned_file($file_path);
                $orphaned_count++;
            }
        }

        return [
            'scanned' => count($batch_files),
            'total' => $total_files,
            'orphaned_found' => $orphaned_count,
            'next_offset' => $offset + count($batch_files),
            'is_complete' => ($offset + count($batch_files)) >= $total_files,
        ];
    }

    /**
     * Get all files in a directory recursively
     *
     * @param string $dir Directory path.
     * @return array Array of file paths.
     */
    private function get_all_files($dir)
    {
        $files = [];

        if (!is_dir($dir)) {
            return $files;
        }

        $iterator = new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS);
        $flatten = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);

        foreach ($flatten as $file) {
            if ($file->isFile()) {
                $path = $file->getPathname();

                // Skip excluded directories
                $skip = false;
                foreach ($this->excluded_dirs as $excluded) {
                    if (strpos($path, '/' . $excluded . '/') !== false) {
                        $skip = true;
                        break;
                    }
                }

                if ($skip) continue;

                // Check file extension
                $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
                if (in_array($ext, $this->allowed_extensions)) {
                    $files[] = $path;
                }
            }
        }

        return $files;
    }

    /**
     * Check if a file is orphaned (not in media library)
     *
     * @param string $file_path Absolute file path.
     * @return bool
     */
    private function is_orphaned($file_path)
    {
        global $wpdb;

        // Check if file is registered as an attachment.
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $attachment = $wpdb->get_var($wpdb->prepare(
            "SELECT ID FROM $wpdb->posts 
             WHERE post_type = 'attachment' 
             AND guid LIKE %s",
            '%' . $wpdb->esc_like(basename($file_path))
        ));

        if ($attachment) {
            return false;
        }

        // Check if file is a thumbnail of a registered attachment
        $upload_dir = wp_upload_dir();
        $relative_path = str_replace($upload_dir['basedir'] . '/', '', $file_path);

        // Check if it's referenced in attachment metadata.
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $is_thumbnail = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM $wpdb->postmeta 
             WHERE meta_key = '_wp_attachment_metadata' 
             AND meta_value LIKE %s",
            '%' . $wpdb->esc_like(basename($file_path)) . '%'
        ));

        if ($is_thumbnail) {
            return false;
        }

        return true;
    }

    /**
     * Save orphaned file to database
     *
     * @param string $file_path Absolute file path.
     */
    private function save_orphaned_file($file_path)
    {
        global $wpdb;

        $file_info = pathinfo($file_path);
        $file_size = filesize($file_path);
        $mime_type = wp_check_filetype($file_path);

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
        $wpdb->insert(
            $this->table_name,
            [
                'file_path' => $file_path,
                'file_name' => $file_info['basename'],
                'file_size' => $file_size,
                'mime_type' => $mime_type['type'] ?: 'application/octet-stream',
                'found_at' => current_time('mysql'),
            ],
            ['%s', '%s', '%d', '%s', '%s']
        );
    }

    /**
     * AJAX handler to get orphaned files
     */
    public function ajax_get_orphaned_files()
    {
        check_ajax_referer('mtrim_admin_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Unauthorized', 'media-trim')]);
        }

        global $wpdb;

        $search = isset($_POST['search']) ? sanitize_text_field(wp_unslash($_POST['search'])) : '';
        $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
        $limit = isset($_POST['limit']) ? absint($_POST['limit']) : 20;
        $offset = ($page - 1) * $limit;

        $where = "1=1";
        $params = [];

        if (!empty($search)) {
            $where .= " AND file_name LIKE %s";
            $params[] = '%' . $wpdb->esc_like($search) . '%';
        }

        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter
        if ( ! empty( $params ) ) {
            $total_items = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$this->table_name} WHERE $where", $params ) );
        } else {
            $total_items = $wpdb->get_var( "SELECT COUNT(*) FROM {$this->table_name} WHERE $where" );
        }
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter

        $query = "SELECT * FROM {$this->table_name} WHERE $where ORDER BY found_at DESC LIMIT %d OFFSET %d";
        $params[] = $limit;
        $params[] = $offset;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is safely constructed, $query is built with placeholders.
        $items = $wpdb->get_results($wpdb->prepare($query, $params));

        $upload_url = wp_upload_dir()['baseurl'];

        foreach ($items as &$item) {
            $item->formatted_size = size_format($item->file_size);
            $ext = strtolower(pathinfo($item->file_path, PATHINFO_EXTENSION));
            $item->is_image = (strpos($item->mime_type, 'image/') === 0 || $ext === 'svg');
            $item->relative_path = str_replace($this->uploads_dir . '/', '', $item->file_path);
            $item->file_url = $upload_url . '/' . $item->relative_path;
        }

        wp_send_json_success([
            'items' => $items,
            'total' => (int) $total_items,
            'pages' => ceil($total_items / $limit),
            'current_page' => $page,
        ]);
    }

    /**
     * AJAX handler to delete an orphaned file
     */
    public function ajax_delete_orphaned_file()
    {
        check_ajax_referer('mtrim_admin_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Unauthorized', 'media-trim')]);
        }

        $file_id = isset($_POST['file_id']) ? absint($_POST['file_id']) : 0;

        if (!$file_id) {
            wp_send_json_error(['message' => __('Invalid file ID', 'media-trim')]);
        }

        global $wpdb;

        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $file = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$this->table_name} WHERE id = %d",
            $file_id
        ));
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter

        if (!$file) {
            wp_send_json_error(['message' => __('File not found', 'media-trim')]);
        }

        // Delete the physical file
        if (file_exists($file->file_path)) {
            wp_delete_file($file->file_path);
        }

        // Remove from database.
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->delete($this->table_name, ['id' => $file_id], ['%d']);

        wp_send_json_success(['message' => __('File deleted successfully', 'media-trim')]);
    }

    /**
     * AJAX handler to add orphaned file to media library
     */
    public function ajax_add_to_media_library()
    {
        check_ajax_referer('mtrim_admin_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Unauthorized', 'media-trim')]);
        }

        $file_id = isset($_POST['file_id']) ? absint($_POST['file_id']) : 0;

        if (!$file_id) {
            wp_send_json_error(['message' => __('Invalid file ID', 'media-trim')]);
        }

        global $wpdb;

        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $file = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$this->table_name} WHERE id = %d",
            $file_id
        ));
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter

        if (!$file || !file_exists($file->file_path)) {
            wp_send_json_error(['message' => __('File not found', 'media-trim')]);
        }

        // Add to media library
        $attachment_id = wp_insert_attachment([
            'post_mime_type' => $file->mime_type,
            'post_title' => pathinfo($file->file_name, PATHINFO_FILENAME),
            'post_content' => '',
            'post_status' => 'inherit',
        ], $file->file_path);

        if ($attachment_id && !is_wp_error($attachment_id)) {
            require_once(ABSPATH . 'wp-admin/includes/image.php');
            $metadata = wp_generate_attachment_metadata($attachment_id, $file->file_path);
            wp_update_attachment_metadata($attachment_id, $metadata);
 
            // Remove from orphaned files table.
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->delete($this->table_name, ['id' => $file_id], ['%d']);
 
            wp_send_json_success([
                'message' => __('File added to media library', 'media-trim'),
                'attachment_id' => $attachment_id,
            ]);
        } else {
            wp_send_json_error(['message' => __('Failed to add file to media library', 'media-trim')]);
        }
    }

    /**
     * Get orphaned files statistics
     *
     * @return array
     */
    public function get_stats()
    {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safely constructed.
        $count = $wpdb->get_var("SELECT COUNT(*) FROM {$this->table_name}");
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safely constructed.
        $size = $wpdb->get_var("SELECT SUM(file_size) FROM {$this->table_name}");

        return [
            'count' => (int) $count,
            'size' => (int) $size,
            'formatted_size' => size_format($size ?: 0),
        ];
    }
}
