<?php

declare(strict_types=1);

namespace App\Http\Controllers\Market;

use App\Http\Controllers\Helpers\ResponseHelper;
use App\Models\Market\PosSession;
use App\Models\Market\PosTransaction;
use App\Models\Market\PosSessionArchive;
use App\Models\Market\Product;
use App\Models\Market\Store;
use Hypervel\Http\Request;
use Hypervel\Support\Facades\Auth;
use Hypervel\Support\Facades\Response;
use Hyperf\Stringable\Str;
use App\Models\Market\PosAccessKey;
use App\Models\Market\Customer;
use Hypervel\Support\Facades\DB;
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
use App\Enums\UserActions;
use Hyperf\Coroutine\Coroutine;
use App\Http\Controllers\Helpers\CacheHelper;

class PosController
{
    public function startSession(Request $request)
    {
        $validated = $request->validate([
            'store_hash' => 'nullable|string',
            'customer_name' => 'nullable|string|max:255',
            'access_key' => 'nullable|string',
        ]);

        $store = null;
        $accessKeyObj = null;

        if ($request->input('access_key')) {
            $accessKeyObj = PosAccessKey::where('access_key', $request->input('access_key'))
                ->where('status', 'active')
                ->first();

            if ($accessKeyObj) {

                if ($accessKeyObj->isExpired()) {
                    $accessKeyObj->status = 'inactive';
                    $accessKeyObj->save();
                    return ResponseHelper::returnError('Access key has expired', 401);
                }

                $store = $accessKeyObj->store;
            } elseif (!Auth::check()) {

                return ResponseHelper::returnError('Invalid or inactive access key', 401);
            }
        }

        if (!$store && !empty($validated['store_hash'])) {
            $store = Store::where('hashkey', $validated['store_hash'])->first();
        }

        if (!$store) {
            return ResponseHelper::returnError('No store found. Please open the POS from a store page or use a valid access key.', 422);
        }

        // Authorization check
        if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $store)) {
            return ResponseHelper::returnError('You are not authorized to start a POS session for this store.', 403);
        }

        /** @var PosSession $session */
        $session = PosSession::create([
            'access_key' => $accessKeyObj ? $accessKeyObj->access_key : Str::random(32),
            'store_id' => $store->id,
            'customer_name' => $validated['customer_name'] ?? null,
            'status' => 'active',
            'created_by' => Auth::id(),
        ]);

        if ($accessKeyObj) {
            $accessKeyObj->last_used_at = now();
            $accessKeyObj->save();
        }

        $this->archiveSession($session, 'Session initialized');
        $this->invalidateSessionCache($session);

        return ResponseHelper::returnSuccessResponse([
            'hashkey' => $session->hashkey,
            'access_key' => $session->access_key,
        ], $session->hashkey, 'POS Session started');
    }

    public function getSession(Request $request)
    {
        $hashkey = ResponseHelper::getTargetHash();
        $accessKey = $request->input('access_key');

        if (!$hashkey && !$accessKey) {
            return ResponseHelper::returnError('No key provided', 400);
        }

        $session = null;

        if ($hashkey) {
            $q = $this->getBaseSessionQuery()->where('hashkey', $hashkey);
            $session = CacheHelper::get_cache($q);
            
            if (!$session) {
                $session = $q->first();
                if ($session) {
                    CacheHelper::set_cache($q, $session);
                }
            }

            // If not a session hash, check if it's a store hash
            if (!$session) {
                $sq = Store::where('hashkey', $hashkey);
                $store = CacheHelper::get_cache($sq);
                if (!$store) {
                    $store = $sq->first();
                    if ($store) {
                        CacheHelper::set_cache($sq, $store);
                    }
                }

                if ($store) {
                    $q = $this->getBaseSessionQuery()
                        ->where('store_id', $store->id)
                        ->where('status', 'active')
                        ->orderBy('id', 'desc');
                    
                    $session = CacheHelper::get_cache($q);
                    if (!$session) {
                        $session = $q->first();
                        if ($session) {
                            CacheHelper::set_cache($q, $session);
                        }
                    }
                }
            }
        }

        // If still no session and we have an accessKey, try that (as fallback or primary if no hashkey)
        if (!$session && $accessKey) {
            $q = $this->getBaseSessionQuery()
                ->where('access_key', $accessKey)
                ->orderBy('id', 'desc');
            
            $session = CacheHelper::get_cache($q);
            if (!$session) {
                $session = $q->first();
                if ($session) {
                    CacheHelper::set_cache($q, $session);
                }
            }
        }

        if (!$session) {
            return ResponseHelper::returnError('Session not found', 404);
        }

        // Authorization check
        if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $session->store_id)) {
            return ResponseHelper::returnError('You are not authorized to access this POS session.', 403);
        }

        // Return the full session with all eager loaded relations
        return ResponseHelper::returnSuccessResponse($session, $session->hashkey);
    }

    private function getBaseSessionQuery()
    {
        return PosSession::with([
            'transactions.product' => function ($q) {
                // Only fetch minimal columns needed for the POS to reduce serialization time
                $q->select(['id', 'hashkey', 'name', 'price', 'photourl', 'unitname', 'category']);
            },
            'store'
        ]);
    }

    public function addItem(Request $request)
    {
        $validated = $request->validate([
            'session_hash' => 'required|string',
            'product_hash' => 'required|string',
            'quantity' => 'required|integer|min:1',
            'price' => 'nullable|numeric|min:0',
        ]);

        $sq = PosSession::where('hashkey', $validated['session_hash']);
        $session = CacheHelper::get_cache($sq);
        if (!$session) {
            $session = $sq->first();
            if ($session) {
                CacheHelper::set_cache($sq, $session);
            }
        }

        $pq = Product::select(['id', 'hashkey', 'price', 'is_active', 'name'])->where('hashkey', $validated['product_hash']);
        $product = CacheHelper::get_cache($pq);
        if (!$product) {
            $product = $pq->first();
            if ($product) {
                CacheHelper::set_cache($pq, $product);
            }
        }

        if (!$session || !$product) {
            return ResponseHelper::returnError('Session or Product not found', 404);
        }

        $sessionNeedsSave = false;
        if ($session->status !== 'active') {
            $session->status = 'active';
            $sessionNeedsSave = true;
        }

        $price = (int) $product->price;
        $isActive = $product->is_active;

        if ($session->store_id) {
            $psq = DB::table('prd_str')
                ->where('store_id', $session->store_id)
                ->where('product_id', $product->id)
                ->select('price', 'is_active');

            $storeProduct = CacheHelper::get_cache($psq);
            if (!$storeProduct) {
                $storeProduct = $psq->first();
                if ($storeProduct) {
                    CacheHelper::set_cache($psq, $storeProduct, [], 3600); // 1 hour
                }
            }

            if ($storeProduct) {
                if (isset($storeProduct->price)) {
                    $price = (int) $storeProduct->price;
                }
                if (isset($storeProduct->is_active)) {
                    $isActive = (bool) $storeProduct->is_active;
                }
            } else {
                return ResponseHelper::returnError('Product not available in this store', 403);
            }
        }

        if (!$isActive) {
            return ResponseHelper::returnError('Product is currently inactive in this store', 403);
        }

        // Use custom price if provided, otherwise use calculated store/product price
        if ($request->has('price')) {
            $price = (int) $validated['price'];
        }

        // Update or create the transaction using raw DB for max speed
        $existingTx = DB::table('pos_transactions')
            ->where('pos_session_id', $session->id)
            ->where('product_id', $product->id)
            ->first();

        if ($existingTx) {
            DB::table('pos_transactions')->where('id', $existingTx->id)->update([
                'quantity' => $validated['quantity'],
                'price_at_sale' => $price,
                'total_price' => $price * $validated['quantity'],
                'updated_at' => now(),
            ]);
        } else {
            DB::table('pos_transactions')->insert([
                'pos_session_id' => $session->id,
                'product_id' => $product->id,
                'quantity' => $validated['quantity'],
                'price_at_sale' => $price,
                'total_price' => $price * $validated['quantity'],
                'hashkey' => \Hyperf\Stringable\Str::uuid()->toString() . \Hyperf\Stringable\Str::random(100),
                'created_at' => now(),
                'updated_at' => now(),
                'created_by' => Auth::id() ?? null,
            ]);
        }

        // Load specific columns to be fast, just like in getSession to reduce payload and memory
        $session->load([
            'transactions.product' => function ($q) {
                $q->select(['id', 'hashkey', 'name', 'price', 'photourl', 'unitname', 'category']);
            },
            'store'
        ]);
        $t_load = microtime(true);

        // Update session totals in memory
        $total = $session->transactions->where('is_void', false)->sum('total_price');

        $updateData = [];
        if ($session->total_amount !== (int) $total) {
            $session->total_amount = (int) $total;
            $updateData['total_amount'] = (int) $total;
        }
        if ($sessionNeedsSave) {
            $updateData['status'] = $session->status;
        }

        // Use raw DB update to skip ModelSavingListener overhead while making sure we still record who updated it
        if (!empty($updateData)) {
            $updateData['updated_at'] = now();
            $updateData['updated_by'] = Auth::id();
            DB::table('pos_sessions')->where('id', $session->id)->update($updateData);
        }
        $t_db = microtime(true);

        // Invalidate all possible session cache keys
        $this->invalidateSessionCache($session);

        // Archive the session using already loaded transaction data (deferred to background coroutine)
        $this->archiveSession($session, 'Item added/updated: ' . $product->name, $session->transactions);

        return ResponseHelper::returnSuccessResponse($session, $session->hashkey, 'Item added to session');
    }

    public function removeItem(Request $request)
    {
        $validated = $request->validate([
            'session_hash' => 'required|string',
            'transaction_id' => 'required|integer',
        ]);

        $session = PosSession::where('hashkey', $validated['session_hash'])->first();
        if (!$session) {
            return ResponseHelper::returnError('Session not found', 404);
        }

        $transaction = PosTransaction::where('id', $validated['transaction_id'])
            ->where('pos_session_id', $session->id)
            ->first();

        if ($transaction) {
            $transaction->delete();

            // Re-calculate and archive efficiently
            // Load relations ONCE with only necessary columns
            $session->load([
                'transactions.product' => function ($q) {
                    $q->select(['id', 'hashkey', 'name', 'price', 'photourl', 'unitname', 'category']);
                },
                'store'
            ]);

            $total = $session->transactions->where('is_void', false)->sum('total_price');
            $session->total_amount = (int) $total;
            DB::table('pos_sessions')->where('id', $session->id)->update([
                'total_amount' => (int) $total,
                'updated_at' => now(),
                'updated_by' => Auth::id(),
            ]);

            // Invalidate all possible session cache keys
            $this->invalidateSessionCache($session);

            $this->archiveSession($session, 'Item removed', $session->transactions);
        } else {
            $session->load([
                'transactions.product' => function ($q) {
                    $q->select(['id', 'hashkey', 'name', 'price', 'photourl', 'unitname', 'category']);
                },
                'store'
            ]);
        }

        return ResponseHelper::returnSuccessResponse($session, $session->hashkey, 'Item removed from session');
    }

    public function completeSession(Request $request)
    {
        $validated = $request->validate([
            'session_hash' => 'required|string',
            'received_amount' => 'required|integer|min:0',
            'payment_method' => 'required|string',
            'payment_field' => 'nullable|string',
            'customer_name' => 'nullable|string|max:255',
        ]);

        $session = PosSession::where('hashkey', $validated['session_hash'])->first();
        if (!$session) {
            return ResponseHelper::returnError('Session not found', 404);
        }

        $session->received_amount = $validated['received_amount'];
        $session->change_amount = $validated['received_amount'] - $session->total_amount;
        $session->payment_method = $validated['payment_method'];
        $session->payment_details = ['payment_field' => $validated['payment_field']];
        if (!empty($validated['customer_name'])) {
            $session->customer_name = $validated['customer_name'];
        }
        $session->status = 'completed';
        $session->save();

        // Invalidate cache
        $this->invalidateSessionCache($session);

        if (!empty($validated['customer_name'])) {
            $customerName = trim($validated['customer_name']);
            $customer = Customer::where('name', $customerName)
                ->where(function ($q) use ($session) {
                    $q->where('store_id', $session->store_id)
                        ->orWhereNull('store_id');
                })
                ->first();

            if (!$customer) {
                Customer::create([
                    'name' => $customerName,
                    'store_id' => $session->store_id,
                    'created_by' => Auth::id(),
                ]);
            } else {
                $customer->updated_at = now();
                $customer->save();
            }
        }

        $this->archiveSession($session, 'Session completed');

        return ResponseHelper::returnSuccessResponse($session, $session->hashkey, 'Transaction completed');
    }

    public function syncOffline(Request $request)
    {
        $validated = $request->validate([
            'transactions' => 'required|array',
            'transactions.*.store_hash' => 'required|string',
            'transactions.*.customer_name' => 'nullable|string',
            'transactions.*.items' => 'required|array',
            'transactions.*.total' => 'required|numeric',
            'transactions.*.received' => 'required|numeric',
            'transactions.*.method' => 'required|string',
            'transactions.*.timestamp' => 'required|string',
            'transactions.*.local_id' => 'nullable|integer',
        ]);

        $syncedCount = 0;
        $syncedIds = [];
        $errors = [];
        $affectedStoreIds = [];

        foreach ($validated['transactions'] as $txn) {
            try {
                DB::beginTransaction();

                $store = Store::where('hashkey', $txn['store_hash'])->first();
                if (!$store) {
                    throw new \Exception('Store not found for hash: ' . $txn['store_hash']);
                }

                // Convert ISO 8601 timestamp to MySQL datetime format
                $offlineTimestamp = date('Y-m-d H:i:s', strtotime($txn['timestamp']));

                // Create the session
                $session = new PosSession([
                    'store_id' => $store->id,
                    'customer_name' => $txn['customer_name'] ?? null,
                    'total_amount' => (int) $txn['total'],
                    'received_amount' => (int) $txn['received'],
                    'change_amount' => (int) ($txn['received'] - $txn['total']),
                    'payment_method' => $txn['method'],
                    'status' => 'completed',
                    'created_by' => Auth::id(),
                    'access_key' => 'synced-' . Str::random(32),
                    'hashkey' => Str::random(32) . '-' . Str::random(100),
                ]);

                // Manually set timestamps to preserve offline time
                $session->created_at = $offlineTimestamp;
                $session->updated_at = $offlineTimestamp;
                $session->save();

                // Add Items
                foreach ($txn['items'] as $item) {
                    $product = Product::where('hashkey', $item['product_hashkey'])->first();
                    if (!$product) continue;

                    DB::table('pos_transactions')->insert([
                        'pos_session_id' => $session->id,
                        'product_id' => $product->id,
                        'quantity' => $item['quantity'],
                        'price_at_sale' => (int) $item['price_at_sale'],
                        'total_price' => (int) ($item['price_at_sale'] * $item['quantity']),
                        'hashkey' => Str::uuid()->toString() . Str::random(100),
                        'created_at' => $offlineTimestamp,
                        'updated_at' => now(),
                        'created_by' => Auth::id(),
                    ]);
                }

                // Handle Customer
                if (!empty($txn['customer_name'])) {
                    $customerName = trim($txn['customer_name']);
                    $customer = Customer::where('name', $customerName)
                        ->where(function ($q) use ($store) {
                            $q->where('store_id', $store->id)
                                ->orWhereNull('store_id');
                        })
                        ->first();

                    if (!$customer) {
                        Customer::create([
                            'name' => $customerName,
                            'store_id' => $store->id,
                            'created_by' => Auth::id(),
                        ]);
                    }
                }

                $this->archiveSession($session, 'Offline synced transaction');
                $this->invalidateSessionCache($session);
                
                DB::commit();
                $syncedCount++;
                
                if (isset($txn['local_id'])) {
                    $syncedIds[] = $txn['local_id'];
                }

                if (!in_array($store->id, $affectedStoreIds)) {
                    $affectedStoreIds[] = $store->id;
                }
            } catch (\Exception $e) {
                DB::rollBack();
                $errors[] = $e->getMessage();
            }
        }

        return ResponseHelper::returnSuccessResponse([
            'synced_count' => $syncedCount,
            'synced_ids' => $syncedIds,
            'errors' => $errors
        ], 'sync_offline', "Synced $syncedCount transactions");
    }

    public function voidSession(Request $request)
    {
        $validated = $request->validate([
            'session_hash' => 'required|string',
            'remarks' => 'nullable|string',
        ]);

        $session = PosSession::where('hashkey', $validated['session_hash'])->first();
        if (!$session) {
            return ResponseHelper::returnError('Session not found', 404);
        }

        if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $session->store_id)) {
            return ResponseHelper::returnError('You are not authorized to void this POS session.', 403);
        }

        $session->status = 'voided';
        $session->is_void = true;
        $session->save();

        // Invalidate cache
        $this->invalidateSessionCache($session);

        $this->archiveSession($session, 'Session voided: ' . ($validated['remarks'] ?? 'No remarks'));

        return ResponseHelper::returnSuccessResponse($session, $session->hashkey, 'Transaction voided');
    }

    public function getPosSessions(Request $request)
    {
        $validated = $request->validate([
            'store_hash' => 'required|string',
            'page' => 'nullable|integer|min:1',
            'per_page' => 'nullable|integer|min:1|max:100',
        ]);

        $store = Store::where('hashkey', $validated['store_hash'])->first();
        if (!$store) {
            return ResponseHelper::returnError('Store not found', 404);
        }

        // Authorization check
        if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $store)) {
            return ResponseHelper::returnError('You are not authorized to view sessions for this store.', 403);
        }

        $page = (int) ($validated['page'] ?? 1);
        $perPage = (int) ($validated['per_page'] ?? 10);
        $offset = ($page - 1) * $perPage;

        $query = PosSession::with(['transactions.product'])
            ->where('store_id', $store->id)
            ->where('status', '!=', 'active')
            ->orderBy('id', 'desc');

        $totalCount = $query->count();
        $sessions = $query->limit($perPage)->offset($offset)->get();

        // Map sessions to include item count and simplify if needed
        $sessions = $sessions->map(function ($session) {
            return [
                'hashkey' => $session->hashkey,
                'status' => $session->status,
                'total_amount' => $session->total_amount,
                'customer_name' => $session->customer_name,
                'payment_method' => $session->payment_method,
                'items_count' => $session->transactions->count(),
                'created_at' => $session->created_at,
                'transactions' => $session->transactions,
            ];
        });

        return ResponseHelper::returnSuccessResponse([
            'sessions' => $sessions,
            'total_count' => $totalCount,
            'page' => $page,
            'per_page' => $perPage,
        ], $store->hashkey, 'POS sessions fetched');
    }

    public function getTodayStats(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewPosReports)) {
            return ResponseHelper::returnUnauthorized();
        }

        $date = now()->format('Y-m-d');
        $query = PosSession::where('status', 'completed')
            ->whereDate('created_at', $date);

        $storeName = null;
        $storePhoto = null;
        // If store_hash is provided, filter by store
        if ($request->input('store_hash')) {
            $store = Store::where('hashkey', $request->input('store_hash'))->first();
            if ($store) {
                // Authorization check
                if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $store)) {
                    return ResponseHelper::returnError('You are not authorized to view reports for this store.', 403);
                }
                $query->where('store_id', $store->id);
                $storeName = $store->name;
                $storePhoto = $store->photourl;
            }
        }
    
        $stats = CacheHelper::get_cache($query);
        if ($stats) {
            return ResponseHelper::returnSuccessResponse($stats, 'today_stats');
        }

        $stats = [
            'count' => (int) $query->count(),
            'total' => (int) $query->sum('total_amount'),
            'store_name' => $storeName,
            'store_photo' => $storePhoto,
        ];

        CacheHelper::set_cache($query, $stats, [], 300); // 5 minutes

        return ResponseHelper::returnSuccessResponse($stats, 'today_stats');
    }

    public function getCustomers(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewCustomers)) {
            return ResponseHelper::returnUnauthorized();
        }

        $queryText = $request->input('query');
        $storeHash = $request->input('store_hash');

        $customerQuery = Customer::where('is_active', true);

        if ($storeHash) {
            $store = Store::where('hashkey', $storeHash)->first();
            if ($store) {
                // Authorization check
                if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $store)) {
                    return ResponseHelper::returnError('You are not authorized to view customers for this store.', 403);
                }
                $customerQuery->where(function ($q) use ($store) {
                    $q->where('store_id', $store->id)
                        ->orWhereNull('store_id');
                });
            }
        }

        if ($queryText) {
            $customerQuery->where('name', 'like', '%' . $queryText . '%');
        }

        $finalQuery = $customerQuery->orderBy('name', 'asc')->limit(10);
        $customers = CacheHelper::get_cache($finalQuery);

        if (!$customers) {
            $customers = $finalQuery->get();
            CacheHelper::set_cache($finalQuery, $customers, [], 3600); // 1 hour
        }

        return ResponseHelper::returnSuccessResponse($customers, 'customer_suggestions');
    }

    private function updateSessionTotals(PosSession $session)
    {
        $total = $session->transactions()->where('is_void', false)->sum('total_price');
        $session->total_amount = (int) $total;
        $session->save();
    }

    private function archiveSession(PosSession $session, string $remarks = '', $transactions = null)
    {
        // Serialize all data NOW before spawning coroutine to avoid context issues
        $sessionData = $session->toArray();
        $sessionId = $session->id;
        $sessionHashkey = $session->hashkey;
        $userId = Auth::id();

        if ($transactions !== null) {
            $transactionsData = $transactions->toArray();
        } else {
            $transactionsData = $session->transactions()->with('product')->get()->toArray();
        }

        // Defer the archive INSERT to a background coroutine so it doesn't block the response
        Coroutine::create(function () use ($sessionId, $sessionHashkey, $sessionData, $transactionsData, $userId, $remarks) {
            try {
                PosSessionArchive::create([
                    'pos_session_id' => $sessionId,
                    'hashkey' => $sessionHashkey,
                    'session_snapshot' => $sessionData,
                    'transactions_snapshot' => $transactionsData,
                    'created_by' => $userId,
                    'remarks' => $remarks,
                ]);
            } catch (\Throwable $e) {
                // Silently fail — archive is non-critical audit data
            }
        });
    }

    private function invalidateSessionCache(PosSession $session)
    {
        // 1. Invalidate by hashkey (with relations)
        CacheHelper::erase_cache($this->getBaseSessionQuery()->where('hashkey', $session->hashkey));

        // 1b. Invalidate simple hashkey lookup (used in addItem)
        CacheHelper::erase_cache(PosSession::where('hashkey', $session->hashkey));

        // 2. Invalidate by store_id (last active session)
        if ($session->store_id) {
            CacheHelper::erase_cache($this->getBaseSessionQuery()
                ->where('store_id', $session->store_id)
                ->where('status', 'active')
                ->orderBy('id', 'desc'));
        }

        // 3. Invalidate by access_key
        if ($session->access_key) {
            CacheHelper::erase_cache($this->getBaseSessionQuery()
                ->where('access_key', $session->access_key)
                ->orderBy('id', 'desc'));
        }
        // 4. Invalidate today stats cache for the store
        if ($session->store_id) {
            $date = now()->format('Y-m-d');
            $statsQuery = PosSession::where('status', 'completed')
                ->whereDate('created_at', $date)
                ->where('store_id', $session->store_id);
            CacheHelper::erase_cache($statsQuery);
        }
    }
}
