<?php

namespace App\Classes\Modules\Transactions\ControllersLogic;


use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Jobs\Commands\V2\ETLPurchaseOrderTransactionV2CommandJob;
use App\Classes\Modules\Bookings\Services\FetchesBooking;
use App\Classes\Modules\Documents\Services\CreatesDocument;
use App\Classes\Modules\Documents\Services\DeletesDocument;
use App\Classes\Modules\Documents\Services\CreatesFiles;
use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject;
use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject;
use App\Classes\Modules\Transactions\Processors\CreatePurchaseOrderTransactionProcessor;
use App\Classes\Modules\Transactions\Processors\ETLPDFPurchaseOrderClaudeProcessor;
use App\Classes\Modules\Transactions\Services\GeneratesTransactionBillNumber;
use App\Classes\Modules\Transactions\Standards\Rules\CanETLPurchaseOrder;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\PaymentMethodType;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Http\Resources\TransactionResource;
use App\Models\Booking;
use App\Models\Document;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Maatwebsite\Excel\Facades\Excel;

class ImportPurchaseOrderTransactionLogic extends AbstractControllerLogic
{

    /**
     * @return array
     */
    protected function notification(): array
    {
        return [
            'title' => 'Update Purchase Order',
            'message' => $this->notificationMessage ?? "You have successfully updated your booking's purchase order",
        ];
    }

    private ?string $notificationMessage = null;

    /** @var FetchesBooking */
    private $fetchesBooking;

    /** @var GeneratesTransactionBillNumber */
    private $generatesTransactionBillNumber;

    /** @var CreatePurchaseOrderTransactionProcessor */
    private $createPurchaseOrderTransactionProcessor;

    /** @var ETLPDFPurchaseOrderClaudeProcessor */
    private $eTLPDFPurchaseOrderClaudeProcessor;

    /** @var CanETLPurchaseOrder */
    private $canETLPurchaseOrder;

    /** @var CreatesDocument */
    private $createsDocument;

    /** @var DeletesDocument */
    private $deletesDocument;

    /** @var CreatesFiles */
    private $createsFile;

    /**
     * ImportPurchaseOrderTransactionLogic constructor.
     * @param FetchesBooking $fetchesBooking
     * @param GeneratesTransactionBillNumber $generatesTransactionBillNumber
     * @param CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor
     * @param ETLPDFPurchaseOrderClaudeProcessor $eTLPDFPurchaseOrderClaudeProcessor
     * @param CanETLPurchaseOrder $canETLPurchaseOrder
     * @param CreatesDocument $createsDocument
     * @param DeletesDocument $deletesDocument
     * @param CreatesFiles $createsFile
     */
    public function __construct(FetchesBooking $fetchesBooking, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor, ETLPDFPurchaseOrderClaudeProcessor $eTLPDFPurchaseOrderClaudeProcessor, CanETLPurchaseOrder $canETLPurchaseOrder, CreatesDocument $createsDocument, DeletesDocument $deletesDocument, CreatesFiles $createsFile)
    {
        $this->fetchesBooking = $fetchesBooking;
        $this->generatesTransactionBillNumber = $generatesTransactionBillNumber;
        $this->createPurchaseOrderTransactionProcessor = $createPurchaseOrderTransactionProcessor;
        $this->eTLPDFPurchaseOrderClaudeProcessor = $eTLPDFPurchaseOrderClaudeProcessor;
        $this->canETLPurchaseOrder = $canETLPurchaseOrder;
        $this->createsDocument = $createsDocument;
        $this->deletesDocument = $deletesDocument;
        $this->createsFile = $createsFile;
    }

    /**
     * @param Request $request
     * @param string $id
     * @return JsonResponse
     * @throws \App\Classes\Exceptions\MalformedRequestException
     */
    public function logic(Request $request, $id = ''): JsonResponse
    {
        Log::info('ImportPurchaseOrderTransactionLogic started for booking ID: ' . ($request->route('id') ?? $id));
        $products = [];

        /** @var Booking $booking */
        $booking = $this->fetchesBooking->execute(['id' => $request->route('id') ?? $id]);

        if ($request->has('products') && is_array($request->input('products'))) {
            $this->canETLPurchaseOrder->passes();

            $products = $request->input('products');

            if(empty($products)){
                $files = $request->input('files', []);
                $maxSize = 150 * 1024; // 150 KB
                $totalSize = 0;

                $base64FilesEN = [];    // files with "EN" in the name
                $base64FilesOther = []; // files without "EN"

                foreach ($files as $index => $file) {
                    if (!isset($file['base64'])) {
                        Log::info("Missing base64 for file index {$index}");
                        continue;
                    }

                    $fileBase64 = $file['base64'];
                    $fileName = $file['name'] ?? "file_{$index}";

                    if (stripos($fileName, 'EN') !== false) {
                        $base64FilesEN[] = $fileBase64;
                        if (preg_match('/^data:application\/pdf;base64,/', $fileBase64)) {
                            $cleanBase64 = preg_replace('/^data:application\/pdf;base64,/', '', $fileBase64);
                        }
                        else{
                            $cleanBase64 = $fileBase64;
                        }

                        $fileContent = base64_decode($cleanBase64, true);
                        if ($fileContent === false) {
                            Log::info("Invalid Base64 for file index {$index}");
                        }

                        $totalSize += strlen($fileContent);
                    } else {
                        $base64FilesOther[] = $fileBase64;
                    }
                }

                //Keep a copy of all PDF files uploaded include pdf file with 'EN' as part of its filename
                $allBase64Files = array_merge($base64FilesEN, $base64FilesOther);

                $deleteDocument = $booking->documents()->where('document_type', DocumentType::ECOMMERCE_PURCHASE_ORDER)->latest()->first();
                if($deleteDocument){
                    $this->deletesDocument->execute($deleteDocument);
                }

                $object = new DocumentObject(DocumentType::ECOMMERCE_PURCHASE_ORDER, $allBase64Files, '', ApprovalStatus::APPROVED, '1688_purchase_orders');

                /** @var Document $document */
                $document = $this->createsDocument->execute($booking, $object);

                $this->createsFile->execute($document, $object);

                //ETL only applicable for pdf file with 'EN' as part of its filename
                if ($totalSize > 0) {
                    if ($totalSize > $maxSize) {
                        // ETLPurchaseOrderTransactionV2CommandJob::dispatch($base64FilesEN, $request->route('id'));
                        $this->runAsJob($base64FilesEN, $request->route('id'));
                        $this->notificationMessage = "Please don’t refresh the page. The page will automatically update when the process is done.";
                        // $this->notificationMessage = "Please refresh page in a few minutes";
                        return $this->response([]);
                    }
                    else{
                        $products = $this->eTLPDFPurchaseOrderClaudeProcessor->execute($base64FilesEN);
                    }
                }
                else{
                    return $this->response([]);
                }
            }
        }
        else {
            $object = new DocumentObject('', $request->input('files'), '', ApprovalStatus::APPROVED, 'imports');
            foreach ($object->getFiles() as $file) {
                $collection = Excel::toCollection(null, json_decode($file)->file_info->original->file, null, null, true);

                $sheet = $collection->first()->skip(1);

                $products = $sheet->map(function ($row) {
                    Log::info('ImportPurchaseOrderTransactionLogic Row data: ' . json_encode($row));
                    $stockCode = $row[0];
                    $description = $row[1];
                    $quantity = $row[2];
                    $unit_price = $row[3];

                    return [
                        'stockCode' => $stockCode,
                        'description' => $description,
                        'quantity' => $quantity,
                        'unit_price' => $unit_price
                    ];
                })->all();
            }

        }

        $billNumber = $this->generatesTransactionBillNumber->execute('PO-');

        $total = collect($products)->sum(function($product){
            return $product['quantity'] * floatval(str_replace(',', '', $product['unit_price']));
        });

        $object = new TransactionObject($billNumber, TransactionType::PURCHASE_ORDER, $booking->company->id, 1,
            1, PaymentMethodType::CASH,
            $total, $total, $booking->fix_currency_id, $booking->fix_currency_id,
            1, 0, 0, null, ApprovalStatus::PENDING_SUBMISSION, $products);


        $transaction = $this->createPurchaseOrderTransactionProcessor->execute($booking, $object);

        return $this->resourceResponse(new TransactionResource($transaction));

    }

    private function runAsJob($files, $bookingId){
        $maxFilesPerBatch = 4;
        $maxBytesPerBatch = 350 * 1024; // 350 KB

        $currentBatch = [];
        $currentBatchSize = 0;
        $fileBatches = [];

        foreach ($files as $index => $fileBase64) {
            if (preg_match('/^data:application\/pdf;base64,/', $fileBase64)) {
                $cleanBase64 = preg_replace('/^data:application\/pdf;base64,/', '', $fileBase64);
            }
            else{
                $cleanBase64 = $fileBase64;
            }
            $fileContent = base64_decode($cleanBase64, true);
            if ($fileContent === false) {
                Log::info("Invalid Base64 for file index {$index}");
                continue; // skip invalid files
            }

            $fileSize = strlen($fileContent); // size in bytes

            // If adding this file would exceed limits, start a new batch
            $wouldExceedFileCount = count($currentBatch) >= $maxFilesPerBatch;
            $wouldExceedTotalSize = ($currentBatchSize + $fileSize) > $maxBytesPerBatch;

            if ($wouldExceedFileCount || $wouldExceedTotalSize) {
                // save current batch
                $fileBatches[] = $currentBatch;
                // reset
                $currentBatch = [];
                $currentBatchSize = 0;
            }

            // Add file to batch
            $currentBatch[] = $fileBase64;
            $currentBatchSize += $fileSize;

        }

        // push final remaining batch
        if (!empty($currentBatch)) {
            $fileBatches[] = $currentBatch;
        }

        $totalBatches = count($fileBatches);
        $groupId = (string) Str::uuid();

        foreach ($fileBatches as $index => $batch) {

            $isLast = ($index === $totalBatches - 1);

            // Calculate total size of this batch
            $batchSize = 0;
            foreach ($batch as $fileBase64) {
                if (preg_match('/^data:application\/pdf;base64,/', $fileBase64)) {
                    $cleanBase64 = preg_replace('/^data:application\/pdf;base64,/', '', $fileBase64);
                }
                else{
                    $cleanBase64 = $fileBase64;
                }
                $fileContent = base64_decode($cleanBase64, true);
                if ($fileContent !== false) {
                    $batchSize += strlen($fileContent);
                }
            }

            // Log batch info before dispatching job
            Log::info("Dispatching Job for Batch #" . ($index + 1), [
                'group_id' => $groupId,
                'batch_index' => $index + 1,
                'total_batches' => $totalBatches,
                'is_last' => $isLast,
                'files_in_batch' => count($batch),
                'batch_size_bytes' => $batchSize,
            ]);

            ETLPurchaseOrderTransactionV2CommandJob::dispatch(
                $batch,
                $bookingId,
                $groupId,
                $index + 1,
                $totalBatches,
                $isLast
            )->delay(now()->addSeconds($index * 10));
        }
    }
}
