<?php

namespace FuseCommerce\Services;

use FuseCommerce\Config\IndexingConfig;

/**
 * Service for handling JSON file storage operations
 */
class FileStorageService {
    /**
     * Initialize WP_Filesystem
     *
     * @return \WP_Filesystem_Base|false
     */
    private function getFilesystem() {
        global $wp_filesystem;

        if ( ! $wp_filesystem ) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
            WP_Filesystem();
        }

        return $wp_filesystem;
    }

    /**
     * Ensure the storage directory exists
     *
     * @return bool True on success, false on failure
     * @throws \Exception If directory creation fails
     */
    public function ensureDirectory(): bool {
        $dir = IndexingConfig::getStorageDir();

        if ( file_exists( $dir ) && is_dir( $dir ) ) {
            return true;
        }

        if ( ! wp_mkdir_p( $dir ) ) {
            throw new \Exception( esc_html( "Failed to create storage directory: {$dir}" ) );
        }

        // Create .htaccess to prevent direct access (optional security measure)
        $htaccess_file = trailingslashit( $dir ) . '.htaccess';
        if ( ! file_exists( $htaccess_file ) ) {
            file_put_contents( $htaccess_file, "# Prevent directory listing\nOptions -Indexes" );
        }

        return true;
    }

    /**
     * Write data to a JSON file (overwrites existing file)
     *
     * @param string $filename File name (not full path)
     * @param array $data Data to write
     * @return bool True on success, false on failure
     * @throws \Exception If write fails
     */
    public function writeJson( string $filename, array $data ): bool {
        $this->ensureDirectory();

        $file_path = IndexingConfig::getFilePath( $filename );

        // Write to temporary file first, then rename (atomic operation)
        $temp_file = $file_path . '.tmp';

        $json = json_encode( $data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );

        if ( $json === false ) {
            throw new \Exception( 'Failed to encode data to JSON: ' . esc_html( json_last_error_msg() ) );
        }

        $bytes_written = file_put_contents( $temp_file, $json, LOCK_EX );

        if ( $bytes_written === false ) {
            throw new \Exception( esc_html( "Failed to write to temporary file: {$temp_file}" ) );
        }

        // Atomic rename
        $wp_filesystem = $this->getFilesystem();
        if ( ! $wp_filesystem->move( $temp_file, $file_path, true ) ) {
            wp_delete_file( $temp_file ); // Clean up temp file
            throw new \Exception( esc_html( "Failed to rename temporary file to: {$file_path}" ) );
        }

        return true;
    }

    /**
     * Append data to a JSON array file
     * Note: This reads the existing file, appends, and writes back
     * For very large files, consider using streamAppendJson instead
     *
     * @param string $filename File name (not full path)
     * @param array $data Data to append
     * @return bool True on success, false on failure
     * @throws \Exception If operation fails
     */
    public function appendToJson( string $filename, array $data ): bool {
        $this->ensureDirectory();

        $file_path = IndexingConfig::getFilePath( $filename );

        $existing_data = [];

        // Read existing data if file exists
        if ( file_exists( $file_path ) ) {
            $json_content = file_get_contents( $file_path );

            if ( $json_content === false ) {
                throw new \Exception( esc_html( "Failed to read file: {$file_path}" ) );
            }

            $existing_data = json_decode( $json_content, true );

            if ( json_last_error() !== JSON_ERROR_NONE ) {
                throw new \Exception( 'Failed to decode existing JSON: ' . esc_html( json_last_error_msg() ) );
            }

            if ( ! is_array( $existing_data ) ) {
                throw new \Exception( 'Existing JSON file does not contain an array' );
            }
        }

        // Merge data
        $merged_data = array_merge( $existing_data, $data );

        // Write back
        return $this->writeJson( $filename, $merged_data );
    }

    /**
     * Stream append JSON objects to file (memory efficient for large datasets)
     * Appends items directly to the end of the JSON array without loading entire file
     *
     * @param string $filename File name (not full path)
     * @param array $items Items to append
     * @return bool True on success
     * @throws \Exception If operation fails
     */
    public function streamAppendJson( string $filename, array $items ): bool {
        $this->ensureDirectory();

        $file_path = IndexingConfig::getFilePath( $filename );

        // If file doesn't exist, create it with opening bracket
        if ( ! file_exists( $file_path ) ) {
            if ( file_put_contents( $file_path, '[', LOCK_EX ) === false ) {
                throw new \Exception( esc_html( "Failed to create file: {$file_path}" ) );
            }
        }

        // For stream operations, we need to use WP_Filesystem methods
        $wp_filesystem = $this->getFilesystem();

        // Read the current file content
        $content = $wp_filesystem->get_contents( $file_path );

        if ( $content === false ) {
            throw new \Exception( esc_html( "Failed to read file: {$file_path}" ) );
        }

        // Find the last closing bracket
        $position = strrpos( $content, ']' );

        if ( $position === false ) {
            throw new \Exception( 'Invalid JSON array structure in file' );
        }

        // Check if we need a comma (file has content other than '[')
        $needs_comma = $position > 1;

        // Build JSON string to append
        $json_items = [];
        foreach ( $items as $item ) {
            $json_items[] = json_encode( $item, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
        }

        $append_string = ( $needs_comma ? ',' : '' ) . implode( ',', $json_items ) . ']';

        // Build new content by replacing the closing bracket
        $new_content = substr( $content, 0, $position ) . $append_string;

        // Write the new content
        if ( ! $wp_filesystem->put_contents( $file_path, $new_content, FS_CHMOD_FILE ) ) {
            throw new \Exception( 'Failed to write to file' );
        }

        return true;
    }

    /**
     * Rename a file
     *
     * @param string $from_filename Source filename
     * @param string $to_filename Destination filename
     * @return bool True on success
     * @throws \Exception If rename fails
     */
    public function renameFile( string $from_filename, string $to_filename ): bool {
        $from_path = IndexingConfig::getFilePath( $from_filename );
        $to_path   = IndexingConfig::getFilePath( $to_filename );

        if ( ! file_exists( $from_path ) ) {
            throw new \Exception( esc_html( "Source file does not exist: {$from_path}" ) );
        }

        $wp_filesystem = $this->getFilesystem();
        if ( ! $wp_filesystem->move( $from_path, $to_path, true ) ) {
            throw new \Exception( esc_html( "Failed to rename file from {$from_path} to {$to_path}" ) );
        }

        return true;
    }

    /**
     * Delete a file
     *
     * @param string $filename File name (not full path)
     * @return bool True on success, false if file doesn't exist
     * @throws \Exception If deletion fails
     */
    public function deleteFile( string $filename ): bool {
        $file_path = IndexingConfig::getFilePath( $filename );

        if ( ! file_exists( $file_path ) ) {
            return false;
        }

        if ( ! wp_delete_file( $file_path ) ) {
            throw new \Exception( esc_html( "Failed to delete file: {$file_path}" ) );
        }

        return true;
    }

    /**
     * Check if a file exists
     *
     * @param string $filename File name (not full path)
     * @return bool True if file exists
     */
    public function fileExists( string $filename ): bool {
        return file_exists( IndexingConfig::getFilePath( $filename ) );
    }

    /**
     * Get file path
     *
     * @param string $filename File name
     * @return string Full file path
     */
    public function getFilePath( string $filename ): string {
        return IndexingConfig::getFilePath( $filename );
    }

    /**
     * Get file URL
     *
     * @param string $filename File name
     * @return string Full file URL
     */
    public function getFileUrl( string $filename ): string {
        return IndexingConfig::getFileUrl( $filename );
    }

    /**
     * Read JSON file and return decoded data
     *
     * @param string $filename File name (not full path)
     * @return array|null Returns null if file doesn't exist or is invalid
     */
    public function readJson( string $filename ): ?array {
        $file_path = IndexingConfig::getFilePath( $filename );

        if ( ! file_exists( $file_path ) ) {
            return null;
        }

        $json_content = file_get_contents( $file_path );

        if ( $json_content === false ) {
            return null;
        }

        $data = json_decode( $json_content, true );

        if ( json_last_error() !== JSON_ERROR_NONE ) {
            return null;
        }

        return $data;
    }

    /**
     * Check if a JSON file exists in the storage directory
     * Alias for fileExists() with explicit JSON context
     *
     * @param string $filename File name (not full path)
     * @return bool True if file exists
     */
    public function jsonFileExists( string $filename ): bool {
        return $this->fileExists( $filename );
    }

    /**
     * Read file contents as string
     *
     * @param string $filename File name (not full path)
     * @return string|null Returns null if file doesn't exist
     */
    public function read( string $filename ): ?string {
        $file_path = IndexingConfig::getFilePath( $filename );

        if ( ! file_exists( $file_path ) ) {
            return null;
        }

        $content = file_get_contents( $file_path );

        if ( $content === false ) {
            return null;
        }

        return $content;
    }
}
