<?php

namespace FuseCommerce\Services;

use FuseCommerce\Config\IndexingConfig;
use FuseCommerce\Models\IndexingState;
use FuseCommerce\Repositories\ProductRepository;

/**
 * Main orchestrator service for product indexing
 * Coordinates the entire indexing process with batch processing and time management
 */
class ProductIndexerService {
    private ProductRepository $product_repository;
    private ProductSerializerService $serializer;
    private CategoryIndexerService $category_indexer;
    private AttributeIndexerService $attribute_indexer;
    private TagIndexerService $tag_indexer;
    private FileStorageService $file_storage;

    private float $start_time;
    private int $max_execution_time;
    private ?float $min_price = null;
    private ?float $max_price = null;

    public function __construct(
        ProductRepository $product_repository,
        ProductSerializerService $serializer,
        CategoryIndexerService $category_indexer,
        AttributeIndexerService $attribute_indexer,
        TagIndexerService $tag_indexer,
        FileStorageService $file_storage
    ) {
        $this->product_repository  = $product_repository;
        $this->serializer          = $serializer;
        $this->category_indexer    = $category_indexer;
        $this->attribute_indexer   = $attribute_indexer;
        $this->tag_indexer         = $tag_indexer;
        $this->file_storage        = $file_storage;
        $this->start_time          = microtime( true );
        $this->max_execution_time  = IndexingConfig::getMaxExecutionTime();
    }

    /**
     * Main entry point: Start or resume indexing process
     *
     * @return array Status information
     */
    public function startOrResumeIndexing(): array {
        try {
            // Load existing state or create new one
            $state = IndexingState::load();

            if ( $state === null ) {
                // New indexing process
                $state = new IndexingState();
                $state->total_products = $this->product_repository->getPublishedProductCount();
                $state->save();
            } elseif ( $state->isCompleted() ) {
                // Previous process completed, start fresh
                $this->resetProcess();
                $state = new IndexingState();
                $state->total_products = $this->product_repository->getPublishedProductCount();
                $state->save();
            } elseif ( $state->hasError() ) {
                // Previous process had error, don't auto-resume
                return [
                    'success' => false,
                    'message' => __( 'Previous indexing process has errors. Please reset before starting again.', 'ftlcommerce-instant-product-search' ),
                    'state'   => $state->toArray(),
                ];
            }

            // Execute indexing phases
            if ( $state->phase === IndexingState::PHASE_TAXONOMIES ) {
                $this->indexTaxonomies( $state );
            }

            if ( $state->phase === IndexingState::PHASE_PRODUCTS ) {
                $completed = $this->indexProductsBatch( $state );

                if ( $completed ) {
                    $this->completeIndexing( $state );
                }
            }

            return [
                'success' => true,
                'message' => $state->isCompleted() ? __( 'Indexing completed', 'ftlcommerce-instant-product-search' ) : __( 'Indexing in progress', 'ftlcommerce-instant-product-search' ),
                'state'   => $state->toArray(),
            ];
        } catch ( \Exception $e ) {
            // Handle errors
            $state = IndexingState::load() ?? new IndexingState();
            $state->markError( $e->getMessage() );
            $state->save();

            error_log( 'FtlCommerce Indexing Error: ' . $e->getMessage() );

            return [
                'success' => false,
                /* translators: %s: Error message */
                'message' => sprintf( __( 'Indexing error: %s', 'ftlcommerce-instant-product-search' ), $e->getMessage() ),
                'state'   => $state->toArray(),
            ];
        }
    }

    /**
     * Index taxonomies (categories, attributes, and tags)
     * This is Phase 1 - runs once per indexing process
     *
     * @param IndexingState $state
     * @return void
     * @throws \Exception
     */
    private function indexTaxonomies( IndexingState $state ): void {
        // Build and save categories
        $categories = $this->category_indexer->buildCategoryTree();
        $this->file_storage->writeJson( IndexingConfig::CATEGORIES_JSON_FILE, $categories );

        // Build and save attributes
        $attributes = $this->attribute_indexer->buildAttributesData();
        $this->file_storage->writeJson( IndexingConfig::ATTRIBUTES_JSON_FILE, $attributes );

        // Build and save tags
        $tags = $this->tag_indexer->buildTagsList();
        $this->file_storage->writeJson( IndexingConfig::TAGS_JSON_FILE, $tags );

        // Move to products phase
        $state->phase = IndexingState::PHASE_PRODUCTS;
        $state->save();
    }

    /**
     * Index a batch of products
     * This is Phase 2 - runs multiple times until all products are indexed
     *
     * @param IndexingState $state
     * @return bool True if indexing is completed, false if more batches needed
     * @throws \Exception
     */
    private function indexProductsBatch( IndexingState $state ): bool {
        // Initialize temp file if first batch
        if ( $state->offset === 0 ) {
            // Create empty JSON array file
            $this->file_storage->writeJson( IndexingConfig::PRODUCTS_TEMP_JSON_FILE, [] );
        }

        $completed = false;

        while ( ! $this->isTimeUp() && ! $completed ) {
            // Check if we've processed all products
            if ( $state->offset >= $state->total_products ) {
                $completed = true;
                break;
            }

            // Get batch of products
            $products = $this->product_repository->getProductBatch(
                $state->offset,
                $state->batch_size
            );

            if ( empty( $products ) ) {
                // No more products
                $completed = true;
                break;
            }

            // Prime caches for this batch
            $product_ids = array_map(
                function ( $product ) {
                    return $product->get_id();
                },
                $products
            );

            $this->product_repository->primeProductCaches( $product_ids );
            $this->product_repository->primeVariationCaches( $product_ids );

            // Serialize products
            $serialized_products = $this->serializer->serializeBatch( $products );

            // Track price range from this batch
            $this->updatePriceRange( $serialized_products );

            // Append to temp JSON file
            $this->file_storage->streamAppendJson(
                IndexingConfig::PRODUCTS_TEMP_JSON_FILE,
                $serialized_products
            );

            // Update state
            $state->offset += count( $products );
            $state->total_processed += count( $products );
            $state->save();

            // Log progress
            $this->logProgress( $state );

            // Small memory cleanup
            unset( $products, $serialized_products, $product_ids );
        }

        return $completed;
    }

    /**
     * Complete the indexing process
     * Rename temp file to final file and mark state as completed
     *
     * @param IndexingState $state
     * @return void
     * @throws \Exception
     */
    private function completeIndexing( IndexingState $state ): void {
        // Rename temp file to final file
        $this->file_storage->renameFile(
            IndexingConfig::PRODUCTS_TEMP_JSON_FILE,
            IndexingConfig::PRODUCTS_JSON_FILE
        );

        // Mark state as completed
        $state->markCompleted();
        $state->save();

        $urls = [
            'product_url'   => $this->file_storage->fileExists( IndexingConfig::PRODUCTS_JSON_FILE )
                ? $this->file_storage->getFileUrl( IndexingConfig::PRODUCTS_JSON_FILE )
                : null,
            'category_url'  => $this->file_storage->fileExists( IndexingConfig::CATEGORIES_JSON_FILE )
                ? $this->file_storage->getFileUrl( IndexingConfig::CATEGORIES_JSON_FILE )
                : null,
            'attribute_url' => $this->file_storage->fileExists( IndexingConfig::ATTRIBUTES_JSON_FILE )
                ? $this->file_storage->getFileUrl( IndexingConfig::ATTRIBUTES_JSON_FILE )
                : null,
            'tag_url'       => $this->file_storage->fileExists( IndexingConfig::TAGS_JSON_FILE )
                ? $this->file_storage->getFileUrl( IndexingConfig::TAGS_JSON_FILE )
                : null,
        ];

        // Build price range data
        $price_range = [];
        if ( $this->min_price !== null && $this->max_price !== null ) {
            $price_range = [
                'min' => $this->min_price,
                'max' => $this->max_price,
            ];
        }

        // Get currency settings
        $currency_settings = $this->serializer->getCurrencySettings();

        $state->setLastStateData( $urls, $price_range, $currency_settings );
    }

    /**
     * Reset the indexing process
     * Delete state and temp files
     *
     * @return void
     */
    public function resetProcess(): void {
        try {
            // Delete state
            IndexingState::delete();

            // Delete temp files
            $this->file_storage->deleteFile( IndexingConfig::PRODUCTS_TEMP_JSON_FILE );
        } catch ( \Exception $e ) {
            error_log( 'FtlCommerce: Error resetting indexing process: ' . $e->getMessage() );
        }
    }

    /**
     * Get current indexing status
     *
     * @return array Status information
     */
    public function getStatus(): array {
        $state = IndexingState::load();

        if ( $state === null ) {
            return [
                'status'         => 'idle',
                'message'        => __( 'No indexing process running', 'ftlcommerce-instant-product-search' ),
                'has_index'      => $this->file_storage->fileExists( IndexingConfig::PRODUCTS_JSON_FILE ),
                'product_url'    => $this->file_storage->fileExists( IndexingConfig::PRODUCTS_JSON_FILE )
                    ? $this->file_storage->getFileUrl( IndexingConfig::PRODUCTS_JSON_FILE )
                    : null,
                'product_size'   => $this->getFileSize( IndexingConfig::PRODUCTS_JSON_FILE ),
                'category_url'   => $this->file_storage->fileExists( IndexingConfig::CATEGORIES_JSON_FILE )
                    ? $this->file_storage->getFileUrl( IndexingConfig::CATEGORIES_JSON_FILE )
                    : null,
                'category_size'  => $this->getFileSize( IndexingConfig::CATEGORIES_JSON_FILE ),
                'attribute_url'  => $this->file_storage->fileExists( IndexingConfig::ATTRIBUTES_JSON_FILE )
                    ? $this->file_storage->getFileUrl( IndexingConfig::ATTRIBUTES_JSON_FILE )
                    : null,
                'attribute_size' => $this->getFileSize( IndexingConfig::ATTRIBUTES_JSON_FILE ),
                'tag_url'        => $this->file_storage->fileExists( IndexingConfig::TAGS_JSON_FILE )
                    ? $this->file_storage->getFileUrl( IndexingConfig::TAGS_JSON_FILE )
                    : null,
                'tag_size'       => $this->getFileSize( IndexingConfig::TAGS_JSON_FILE ),
            ];
        }

        $response = [
            'status'         => $state->status,
            'progress'       => $state->getProgressPercentage(),
            'total_products' => $state->total_products,
            'processed'      => $state->total_processed,
            'phase'          => $state->phase,
            'started_at'     => $state->started_at,
            'updated_at'     => $state->updated_at,
            'is_stale'       => $state->isStale(),
            'errors'         => $state->errors,
        ];

        if ( $state->isCompleted() ) {
            $response['has_index']       = true;
            $response['product_url']     = $this->file_storage->getFileUrl( IndexingConfig::PRODUCTS_JSON_FILE );
            $response['product_size']    = $this->getFileSize( IndexingConfig::PRODUCTS_JSON_FILE );
            $response['category_url']    = $this->file_storage->getFileUrl( IndexingConfig::CATEGORIES_JSON_FILE );
            $response['category_size']   = $this->getFileSize( IndexingConfig::CATEGORIES_JSON_FILE );
            $response['attribute_url']   = $this->file_storage->getFileUrl( IndexingConfig::ATTRIBUTES_JSON_FILE );
            $response['attribute_size']  = $this->getFileSize( IndexingConfig::ATTRIBUTES_JSON_FILE );
            $response['tag_url']         = $this->file_storage->getFileUrl( IndexingConfig::TAGS_JSON_FILE );
            $response['tag_size']        = $this->getFileSize( IndexingConfig::TAGS_JSON_FILE );
        }

        return $response;
    }

    /**
     * Update min/max price range from a batch of serialized products
     *
     * @param array $serialized_products
     * @return void
     */
    private function updatePriceRange( array $serialized_products ): void {
        foreach ( $serialized_products as $product ) {
            $prices = [];

            // Extract price based on product type
            if ( isset( $product['price'] ) && $product['price'] !== null ) {
                $prices[] = (float) $product['price'];
            }

            // For variable products, also check price_range
            if ( isset( $product['price_range'] ) && is_array( $product['price_range'] ) ) {
                if ( isset( $product['price_range']['min'] ) && $product['price_range']['min'] !== null ) {
                    $prices[] = (float) $product['price_range']['min'];
                }
                if ( isset( $product['price_range']['max'] ) && $product['price_range']['max'] !== null ) {
                    $prices[] = (float) $product['price_range']['max'];
                }
            }

            // Update global min/max
            foreach ( $prices as $price ) {
                if ( $this->min_price === null || $price < $this->min_price ) {
                    $this->min_price = $price;
                }
                if ( $this->max_price === null || $price > $this->max_price ) {
                    $this->max_price = $price;
                }
            }
        }
    }

    /**
     * Check if execution time limit is reached
     *
     * @return bool True if time is up
     */
    private function isTimeUp(): bool {
        $elapsed = microtime( true ) - $this->start_time;
        return $elapsed >= $this->max_execution_time;
    }

    /**
     * Log progress information
     *
     * @param IndexingState $state
     * @return void
     */
    private function logProgress( IndexingState $state ): void {
        error_log( sprintf(
            'FtlCommerce: Indexing progress: %d/%d products (%.2f%%)',
            $state->total_processed,
            $state->total_products,
            $state->getProgressPercentage()
        ) );
    }

    /**
     * Check if WooCommerce is active
     *
     * @return bool
     */
    public function isWooCommerceActive(): bool {
        return class_exists( 'WooCommerce' );
    }

    /**
     * Get filesize for a specific file in formatted MB
     *
     * @param string $filename The filename to check
     * @return string|null Formatted filesize string (e.g., "2.45 MB") or null if file doesn't exist
     */
    private function getFileSize( string $filename ): ?string {
        $file_path = $this->file_storage->getFilePath( $filename );
        if ( file_exists( $file_path ) ) {
            $size_bytes = filesize( $file_path );
            return $this->formatFileSize( $size_bytes );
        }
        return null;
    }

    /**
     * Format bytes into MB with 2 decimal places
     *
     * @param int $bytes The size in bytes
     * @return string Formatted size string (e.g., "2.45 MB")
     */
    private function formatFileSize( int $bytes ): string {
        $mb = $bytes / 1048576; // Convert to MB (1024 * 1024)
        return number_format( $mb, 2 ) . ' MB';
    }
}
