<?php

/**
 * Admin - File Management
 *
 * This class is to handle files updating status, approve, reject and delete
 *
 * @package EASYFUA
 */
// Prevent direct access.
defined( 'ABSPATH' ) || exit;
/**
 * Summary of EASYFUA_File_Manager
 */
class EASYFUA_File_Manager {
    /**
     * File submission ID.
     *
     * @var int $submission_id File submission id.
     */
    private $submission_id;

    /**
     * Main File Manager constructor (setup tables).
     *
     * @param mixed $submission_id File submission id.
     */
    public function __construct( $submission_id = '' ) {
        global $wpdb;
        $this->submission_id = absint( $submission_id );
    }

    /*
    	======================================================
    * FILE ACTIONS
    * ======================================================
    */
    /**
     * This function mostly handle approving file.
     *
     * @param int $file_id File id of the file.
     * @return void|bool|int
     */
    public function approve_file( $file_id ) {
        global $wpdb;
        if ( !$file_id ) {
            return;
        }
        // Verify user capabilities.
        if ( !current_user_can( 'edit_posts' ) ) {
            wp_die( esc_html__( 'Insufficient permissions.', 'easy-file-upload-approval' ) );
        }
        // Don't update if the file is already approved.
        if ( $this->is_approved( $file_id ) ) {
            return;
        }
        // Get current user id.
        $user_id = ( is_user_logged_in() ? get_current_user_id() : 0 );
        // phpcs:ignore
        $updated = $wpdb->update(
            $wpdb->prefix . 'easyfua_files',
            array(
                'file_status' => 'approved',
                'approved_by' => $user_id,
                'updated_at'  => current_time( 'mysql' ),
            ),
            array(
                'id' => $file_id,
            ),
            array('%s', '%d', '%s'),
            array('%d')
        );
        if ( false !== $updated && $this->submission_id ) {
            $this->update_submission_status( $this->submission_id );
        }
        return $updated;
    }

    /**
     * Handle file rejection and admin can add custom note.
     *
     * @param int    $file_id A file id of the file.
     * @param string $note    Rejection note from reviewer.
     * @return null|bool|int True if successfully rejected the file.
     */
    public function reject_file( $file_id, $note = null ) {
        global $wpdb;
        // No file id bail early.
        if ( !$file_id ) {
            return;
        }
        // Verify user capabilities.
        if ( !current_user_can( 'edit_posts' ) ) {
            wp_die( esc_html__( 'Insufficient permissions.', 'easy-file-upload-approval' ) );
        }
        // Get user id of approver.
        $user_id = ( is_user_logged_in() ? get_current_user_id() : 0 );
        // Begin updating file status.
        // phpcs:ignore
        $updated = $wpdb->update(
            $wpdb->prefix . 'easyfua_files',
            array(
                'file_status' => 'rejected',
                'approved_by' => $user_id,
                'updated_at'  => current_time( 'mysql' ),
                'admin_note'  => wp_kses_post( $note ),
            ),
            array(
                'id' => $file_id,
            ),
            array(
                '%s',
                '%d',
                '%s',
                '%s'
            ),
            array('%d')
        );
        if ( false !== $updated && $this->submission_id ) {
            $this->update_submission_status( $this->submission_id );
        }
        return $updated;
    }

    /**
     * Delete file submission and files by submission id.
     *
     * @param mixed $submission_id File submission ID.
     * @return void
     */
    public function delete_submission( $submission_id ) {
        global $wpdb;
        if ( !$submission_id ) {
            return;
        }
        // Verify user capabilities.
        if ( !current_user_can( 'edit_posts' ) ) {
            wp_die( esc_html__( 'Insufficient permissions.', 'easy-file-upload-approval' ) );
        }
        // Delete submission entry in submission table.
        // phpcs:ignore
        $submission_deleted = $wpdb->delete( $wpdb->prefix . 'easyfua_submissions', array(
            'id' => absint( $submission_id ),
        ), array('%d') );
        // Delete files under each submisison.
        if ( false !== $submission_deleted ) {
            // Get submission files by id.
            $submission_files = $this->get_submission_files( $submission_id );
            // Make sure we have submission files.
            if ( !empty( $submission_files ) ) {
                foreach ( $submission_files as $file ) {
                    $file_id = ( isset( $file['id'] ) ? absint( $file['id'] ) : null );
                    $this->delete_file( $file_id );
                }
            }
        }
    }

    /**
     * Handle file deletion.
     *
     * @param int $file_id   File id of the file.
     * @return null|bool|int True if file successfully deleted.
     */
    public function delete_file( $file_id ) {
        global $wpdb;
        if ( !$file_id ) {
            return;
        }
        // Verify user capabilities.
        if ( !current_user_can( 'edit_posts' ) ) {
            wp_die( esc_html__( 'Insufficient permissions.', 'easy-file-upload-approval' ) );
        }
        // Delete or unlink file.
        $file = $this->get_file( $file_id );
        $file_url = ( isset( $file['file_url'] ) ? esc_url_raw( $file['file_url'] ) : null );
        // Check if file is in the correct path and safe to delete.
        if ( easyfua_safe_to_delete( $file_url ) ) {
            // Delete database record.
            // phpcs:ignore
            $deleted = $wpdb->delete( $wpdb->prefix . 'easyfua_files', array(
                'id' => absint( $file_id ),
            ), array('%d') );
            if ( false !== $deleted ) {
                $this->update_total_files();
                // updating total_files count.
                // Update submission status.
                if ( $this->submission_id ) {
                    $this->update_submission_status( $this->submission_id );
                }
            }
        }
        return $deleted;
    }

    /*
    ======================================================
     * STATUS HELPERS
     * ======================================================
    */
    /**
     * Update Counts in submission table.
     *
     * @return null|bool True if successfully updating the total_files in submission table.
     */
    public function update_total_files() {
        global $wpdb;
        if ( !$this->submission_id ) {
            return;
        }
        // Get total files.
        // phpcs:ignore
        $total_files = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}easyfua_files WHERE submission_id = %d", $this->submission_id ) );
        // Update total_files in submission table.
        // phpcs:ignore
        return $wpdb->update(
            $wpdb->prefix . 'easyfua_submissions',
            array(
                'total_files' => $total_files,
            ),
            array(
                'id' => $this->submission_id,
            ),
            array('%d'),
            array('%d')
        );
    }

    /**
     * Check wheather the file is already reviewed or not.
     *
     * @param int $file_id File id.
     * @return bool|null   True if file is reviewed or rejected.
     */
    public function is_reviewed( $file_id ) {
        global $wpdb;
        if ( !$file_id ) {
            return;
        }
        // phpcs:ignore
        $status = $wpdb->get_var( $wpdb->prepare( "SELECT status FROM {$wpdb->prefix}easyfua_files WHERE id = %d", absint( $file_id ) ) );
        return in_array( $status, array('approved', 'rejected'), true );
    }

    /**
     * Check if file is already approved or not.
     *
     * @param int $file_id File id of the file.
     * @return null|bool True if file is already approved.
     */
    public function is_approved( $file_id ) {
        global $wpdb;
        if ( !$file_id ) {
            return;
        }
        // Get current status of the file.
        // phpcs:ignore
        $current_status = $wpdb->get_var( $wpdb->prepare( "SELECT file_status FROM {$wpdb->prefix}easyfua_files WHERE id = %d", absint( $file_id ) ) );
        // Check if status is approved and return true otherwise false.
        if ( 'approved' === $current_status ) {
            return true;
        }
        return false;
    }

    /**
     * Get submission files by status.
     *
     * @param string $status Status of the file.
     * @return string|null|int Submission total or null if not found.
     */
    public function get_submission_status( $status = '' ) {
        global $wpdb;
        // Build initial query.
        $sql = "SELECT status, COUNT(*) as total FROM {$wpdb->prefix}easyfua_submissions";
        // If status is provided.
        if ( $status ) {
            $query = $wpdb->get_row( $wpdb->prepare( "SELECT status, COUNT(*) as total FROM {$wpdb->prefix}easyfua_submissions WHERE status = %s", sanitize_text_field( $status ) ), ARRAY_A );
        } else {
            $query = $wpdb->get_row( "SELECT status, COUNT(*) as total FROM {$wpdb->prefix}easyfua_submissions", ARRAY_A );
        }
        // Return number format.
        if ( isset( $query['total'] ) && $query['total'] > 0 ) {
            return number_format( $query['total'] );
        }
        return 0;
    }

    /**
     * Get all submission data/object.
     *
     * @param int $submission_id  File submission id.
     * @return mixed $submissions All data from submission.
     */
    public function get_submission( $submission_id ) {
        global $wpdb;
        if ( !$submission_id ) {
            return;
        }
        $submissions = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}easyfua_submissions WHERE id = %s", absint( $submission_id ) ) );
        if ( $submissions ) {
            return $submissions;
        }
    }

    /**
     * Get all files by submission id.
     *
     * @param int $submission_id The submission id from submission table.
     * @return array|null File data or null if not found.
     */
    public function get_submission_files( $submission_id ) {
        global $wpdb;
        if ( !$submission_id ) {
            return;
        }
        // phpcs:ignore
        return $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}easyfua_files WHERE submission_id = %d", absint( $submission_id ) ), ARRAY_A );
    }

    /**
     * Get a single file by ID.
     *
     * @param int $file_id The file ID.
     * @return array|null File data or null if not found.
     */
    public function get_file( $file_id ) {
        global $wpdb;
        if ( !$file_id ) {
            return;
        }
        // phpcs:ignore
        return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}easyfua_files WHERE id = %d", absint( $file_id ) ), ARRAY_A );
    }

    /**
     * Check if all files in a submission are reviewed (no pending files).
     *
     * @param int $submission_id       File submission id.
     * @return null|int $pending_count Return number of pending count.
     */
    public function all_files_reviewed( $submission_id ) {
        global $wpdb;
        if ( !$submission_id ) {
            return;
        }
        // phpcs:ignore
        $pending_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}easyfua_files WHERE submission_id = %d AND file_status = 'pending'", absint( $submission_id ) ) );
        return 0 === $pending_count;
    }

    /**
     * Update is_notified notification status.
     *
     * @param int  $submission_id File submission id.
     * @param bool $notify      1 or 1 - '1' is being notified and '0' no yet notified.
     * @return bool|null
     */
    public function set_notified( $submission_id, $notify = 0 ) {
        global $wpdb;
        if ( !$submission_id ) {
            return;
        }
        // Update file_meta table.
        // phpcs:ignore
        $notified = $wpdb->update(
            $wpdb->prefix . 'easyfua_submissions',
            array(
                'is_notified' => (int) $notify,
            ),
            array(
                'id' => intval( $submission_id ),
            ),
            array('%d'),
            array('%d')
        );
        if ( false !== $notified ) {
            return true;
        }
    }

    /**
     * Update the submission status based on its files.
     *
     * @param int $submission_id File submission id.
     * @return bool|null Excecute easyfua_all_files_reviewed if all files are reviewed.
     */
    public function update_submission_status( $submission_id ) {
        global $wpdb;
        // If not submission id bail early.
        if ( !$submission_id ) {
            return;
        }
        // Update files table.
        // phpcs:ignore
        $files = $wpdb->get_results( $wpdb->prepare( "SELECT file_status FROM {$wpdb->prefix}easyfua_files WHERE submission_id = %d", intval( $submission_id ) ), ARRAY_A );
        // No files? bail early.
        if ( empty( $files ) ) {
            return false;
        }
        // Initial Status.
        $total_files = count( $files );
        $counts = array(
            'approved' => 0,
            'rejected' => 0,
        );
        foreach ( $files as $file ) {
            if ( isset( $counts[$file['file_status']] ) ) {
                ++$counts[$file['file_status']];
            }
        }
        // Determine the new status.
        if ( $counts['pending'] === $total_files ) {
            $new_status = 'pending';
        } elseif ( $counts['approved'] === $total_files ) {
            $new_status = 'approved';
        } elseif ( $counts['rejected'] === $total_files ) {
            $new_status = 'rejected';
        } else {
            $new_status = 'partial';
            // Mixed results (pending, rejected).
        }
        // Update the submissions table.
        // phpcs:ignore
        $wpdb->update(
            $wpdb->prefix . 'easyfua_submissions',
            array(
                'status'     => $new_status,
                'updated_at' => current_time( 'mysql' ),
            ),
            array(
                'id' => intval( $submission_id ),
            ),
            array('%s', '%s'),
            array('%d')
        );
        // Get submission data.
        $submission = $this->get_submission( $submission_id );
        /**
         * If no pending files remain, trigger hook to send an email.
         */
        if ( 0 === $counts['pending'] && 0 === (int) $submission->is_notified ) {
            $this->set_notified( $submission_id, 1 );
            // set is_notified user to "1" = notified.
            do_action( 'easyfua_all_files_reviewed', $submission_id, $new_status );
        }
        return true;
    }

    /**
     * Download files as a ZIP by specific file IDs.
     *
     * @param array|null $file_ids Optional. Download only selected files by ID.
     */
    public function download_files( $file_ids = null ) {
        global $wpdb;
        // Validate input.
        if ( empty( $file_ids ) ) {
            wp_die( esc_html__( 'No files specified for download.', 'easy-file-upload-approval' ) );
        }
        // Verify user capabilities.
        if ( !current_user_can( 'edit_posts' ) ) {
            wp_die( esc_html__( 'Insufficient permissions.', 'easy-file-upload-approval' ) );
        }
        // Default vars.
        $files = array();
        // Parse file ids.
        if ( is_array( $file_ids ) ) {
            $file_ids_placeholder = implode( ',', array_fill( 0, count( $file_ids ), '%d' ) );
            // Sanitize file ids.
            $prepare_values = array_map( 'absint', $file_ids );
            // Fetch files.
            $files = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}easyfua_files WHERE id IN ( {$file_ids_placeholder} )", $prepare_values ), ARRAY_A );
        }
        // Proceed with the download.
        if ( !empty( $files ) ) {
            $this->download( $files );
        }
    }

    /**
     * Bulk download files in Zip file.
     *
     * @param array $files Download file in zip.
     * @return never
     */
    private function download( $files ) {
        global $wp_filesystem;
        // no files? bail early.
        if ( empty( $files ) ) {
            wp_die( esc_html__( 'No files found for download.', 'easy-file-upload-approval' ) );
        }
        // Create ZIP.
        $zip = new ZipArchive();
        // Load WP FileSystem.
        if ( !$wp_filesystem ) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
            WP_Filesystem();
        }
        // Getting submission id or else use timestamp.
        $submission_id = $files[0]['submission_id'] ?? time();
        // Check if temp dir is writable.
        $upload_dir = easyfua_dir();
        $temp_dir = ( $wp_filesystem->is_writable( sys_get_temp_dir() ) ? sys_get_temp_dir() : $upload_dir['dir'] . '/temp' );
        if ( !$wp_filesystem->is_dir( $temp_dir ) ) {
            wp_mkdir_p( $temp_dir );
        }
        // Generate zip name.
        $zip_name = apply_filters( 'easyfua_zip_filename', 'easyfua-submission-' . absint( $submission_id ) . '.zip', $temp_dir );
        // Full path of zip name.
        $zip_filename = trailingslashit( $temp_dir ) . $zip_name;
        // Trying to open the temp zip.
        if ( $zip->open( $zip_filename, ZipArchive::CREATE ) !== true ) {
            wp_die( esc_html__( 'Could not create ZIP file.', 'easy-file-upload-approval' ) );
        }
        // Loop each file and attempt to add in zip.
        foreach ( $files as $file ) {
            $filename = wp_basename( $file['file_name'] ?? '' );
            $file_path = realpath( trailingslashit( $upload_dir['dir'] ) . $filename );
            if ( file_exists( $file_path ) ) {
                $zip->addFile( $file_path, $filename );
            }
        }
        $zip->close();
        // Read raw content.
        $file_content = $wp_filesystem->get_contents( $zip_filename );
        // Force download.
        nocache_headers();
        header( 'Content-Type: application/zip' );
        header( 'Content-Disposition: attachment; filename="' . wp_basename( $zip_name ) . '"' );
        header( 'Content-Length: ' . filesize( $zip_filename ) );
        // Output buffering.
        if ( ob_get_level() ) {
            ob_end_clean();
        }
        // Echo the zip content.
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is intentional binary output (ZIP file content) for download, escaping would corrupt it.
        echo $file_content;
        // Temp clean up.
        $wp_filesystem->delete( $zip_filename, false, 'f' );
        exit;
    }

    /**
     * Generate a secure download URL for files by submission or file IDs.
     *
     * @param int|null   $submission_id Id of the submission.
     * @param array|null $file_ids      Multiple ids of the files.
     * @return string
     */
    public function get_download_url( $submission_id = null, $file_ids = null ) {
        $args = array(
            'action' => 'easyfua_download_files',
        );
        if ( $submission_id ) {
            $args['submission_id'] = absint( $submission_id );
        }
        if ( !empty( $file_ids ) && is_array( $file_ids ) ) {
            $args['file_ids'] = implode( ',', array_map( 'absint', $file_ids ) );
        }
        return esc_url( add_query_arg( $args, admin_url( 'admin-post.php' ) ) );
    }

}
