<?php
namespace App\Services;

use App\Events\DataBatchUpdated;
use App\Models\Campaign;
use App\Models\Category;
use App\Models\Customer;
use App\Models\CustomerAddress;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Outlet;
use App\Models\OutletCampaign;
use App\Models\OutletInventory;
use App\Models\Product;
use App\Models\ProductLike;
use App\Models\ProductReview;
use App\Models\ProductVariant;
use App\Models\Purchase;
use App\Models\PurchaseItem;
use App\Models\RawMaterial;
use App\Models\ReceiptItem;
use App\Models\StockCard;
use App\Models\Supplier;
use App\Models\SyncDelete;
use App\Models\Tenant;
use App\Models\TenantPaymentMethod;
use App\Models\Transaction;
use App\Models\Unit;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;

class SyncService
{
    protected static $columnCache = [];
    protected static $softDeleteCache = [];
    protected array $tableMap = [
        'campaigns'             => Campaign::class,
        'categories'            => Category::class,
        'customers'             => Customer::class,
        'customer_address'      => CustomerAddress::class,
        'orders'                => Order::class,
        'order_items'           => OrderItem::class,
        'outlets'               => Outlet::class,
        'outlet_campaigns'      => OutletCampaign::class,
        'outlet_inventories'    => OutletInventory::class,
        'products'              => Product::class,
        'product_likes'         => ProductLike::class,
        'product_reviews'       => ProductReview::class,
        'product_variants'      => ProductVariant::class,
        'purchases'             => Purchase::class,
        'purchase_items'        => PurchaseItem::class,
        'raw_materials'         => RawMaterial::class,
        'receipt_items'         => ReceiptItem::class,
        'stock_cards'           => StockCard::class,
        'suppliers'             => Supplier::class,
        'sync_deletes'          => SyncDelete::class,
        'tenants'               => Tenant::class,
        'tenant_payment_methods'=> TenantPaymentMethod::class,
        'transactions'          => Transaction::class,
        'units'                 => Unit::class,
        'users'                 => User::class,
    ];

    public function getDeltaSync($lastSync)
    {
        $tenantId = Auth::user()->tenant_id;
        return [
            'tenants' => $this->tableMap['tenants']::withoutGlobalScopes()
            ->where('uuid', $tenantId)
            ->where('updated_at', '>', $lastSync)
            ->first(),
            'tenant_payment_methods' => $this->tableMap['tenant_payment_methods']::where('updated_at', '>', $lastSync)->get(),
            'campaigns' => $this->tableMap['campaigns']::withTrashed()->where('updated_at', '>', $lastSync)->get(),
            'outlets' => $this->tableMap['outlets']::withTrashed()->where('updated_at', '>', $lastSync)->get(),
            'outlet_campaigns' => $this->tableMap['outlet_campaigns']::withTrashed()->where('updated_at', '>', $lastSync)->get(),
            'users' => $this->tableMap['users']::withTrashed()->where('updated_at', '>', $lastSync)->get(),
            'units' => $this->tableMap['units']::where('updated_at', '>', $lastSync)->get(),
            'categories' => $this->tableMap['categories']::where('updated_at', '>', $lastSync)->get(),
            'products' => $this->tableMap['products']::withTrashed()->where('updated_at', '>', $lastSync)->get(),
            'product_variants' => $this->tableMap['product_variants']::withTrashed()->where('updated_at', '>', $lastSync)->get(),
            'raw_materials' => $this->tableMap['raw_materials']::withTrashed()->where('updated_at', '>', $lastSync)->get(),
            'receipt_items' => $this->tableMap['receipt_items']::withTrashed()->where('updated_at', '>', $lastSync)->get(),
            'outlet_inventories' => $this->tableMap['outlet_inventories']::withTrashed()->where('updated_at', '>', $lastSync)->get(),
            'customers' => $this->tableMap['customers']::withTrashed()->where('updated_at', '>', $lastSync)->get(),
            'customer_address' => $this->tableMap['customer_address']::withTrashed()->where('updated_at', '>', $lastSync)->get(),
            'product_likes' => $this->tableMap['product_likes']::where('updated_at', '>', $lastSync)->get(),
            'product_reviews' => $this->tableMap['product_reviews']::where('updated_at', '>', $lastSync)->get(),
            'orders' => $this->tableMap['orders']::where('updated_at', '>', $lastSync)->get(),
            'order_items' => $this->tableMap['order_items']::with(['variant' => function($q) {
                $q->select('uuid','product_id')->withTrashed();
            }])->where('updated_at', '>', $lastSync)->get(),
            'suppliers' => $this->tableMap['suppliers']::withTrashed()->where('updated_at', '>', $lastSync)->get(),
            'purchases' => $this->tableMap['purchases']::where('updated_at', '>', $lastSync)->get(),
            'purchase_items' => $this->tableMap['purchase_items']::with(['item' => function (MorphTo $morphTo) {
                    $morphTo->constrain([
                        ProductVariant::class => fn($q) => 
                            $q->select('uuid', 'product_id')->withTrashed(),

                        RawMaterial::class => fn($q) => 
                            $q->select('uuid')->withTrashed(),
                    ]);
                }
            ])->where('updated_at', '>', $lastSync)->get(),
            'transactions' => $this->tableMap['transactions']::where('updated_at', '>', $lastSync)->get(),
            'stock_cards' => $this->tableMap['stock_cards']::where('created_at', '>', $lastSync)->get(),
            'sync_deletes' => $this->tableMap['sync_deletes']::where('created_at', '>', $lastSync)->get()
        ];
    }

    public function updateServerTime(Carbon $currentTime)
    {
        Auth::user()->update(['last_sync' => $currentTime]);
    }

    public function processBatchSync(array $payload)
    {
        $items = $payload['items'];
        $results = ['success_count' => 0, 'ignored_count' => 0, 'errors' => []];
        $changedTables = [];

        foreach ($items as $item) {
            try {
                $tableName = $item['table'];
                $uuid      = $item['local_id']; 
                $action    = $item['action'];
                $data      = $item['data'];

                $modelClass = $this->tableMap[$tableName] ?? null;

                if (!$modelClass) throw new \Exception("Table {$tableName} not mapped.");

                DB::transaction(function () use ($modelClass, $uuid, $data, $action, $tableName, &$results, &$changedTables) {
                    if ($action === 'bulk_update') {
                        if($tableName === 'product_variants') {
                            $visibleIds = $data['visible_ids'] ?? [];
    
                            $modelClass::whereNotIn('uuid', $visibleIds)
                                ->update(['is_visible_online' => false, 'updated_at' => $data['updated_at']]);
    
                            $modelClass::whereIn('uuid', $visibleIds)
                                ->update(['is_visible_online' => true, 'updated_at' => $data['updated_at']]);
    
                            $results['success_count']++;
                            $changedTables[] = 'product_variants';
                            return;
                        }

                        if($tableName === 'outlet_campaigns') {
                            $ids = $data['ids'] ?? [];

                            $modelClass::whereIn('uuid', $ids)
                            ->update(['deleted_at' => $data['deleted_at']]);

                            $results['success_count']++;
                            $changedTables = 'outlet_campaigns';
                            return;
                        }
                    }
                    
                    $usesSoftDeletes = $this->usesSoftDeletes($modelClass);

                    if($tableName === 'tenants') {
                        $query = $modelClass::withoutGlobalScopes()->where('uuid', $uuid);
                    } else {
                        $query = $modelClass::where('uuid', $uuid);
                    }

                    if (in_array($tableName, ['product_variants','outlet_inventories', 'stock_cards'])) {
                        $query->lockForUpdate();
                    }

                    if($usesSoftDeletes) {
                        $query->withTrashed();
                    }

                    $existing = $query->first();

                    $deltaTotalSold = 0;
                    if ($tableName === 'product_variants' && isset($data['qty_sold'])) {
                        $deltaTotalSold = (float) $data['qty_sold'];
                        unset($data['qty_sold']); 
                    }

                    $shouldUpdateMetadata = true;
                    if ($existing && isset($data['updated_at'])) {
                        if (Carbon::parse($data['updated_at'])->lte($existing->updated_at)) {
                            $shouldUpdateMetadata = false; 
                            $results['ignored_count']++;
                        }
                    }
                    
                    if($shouldUpdateMetadata) {
                        if (strtoupper($action) === 'DELETE') {
                            $this->handleSoftDelete($modelClass, $uuid, $data);
                        } else {
                            if ($tableName === 'stock_cards') {
                                $this->handleDeltaStock($data);
                                $modelClass::create($data);
                            } else {
                                if($tableName === 'outlets' && isset($data['is_main_outlet']) && $data['is_main_outlet'] === true) {
                                    Outlet::whereNot('uuid', $uuid)->update(['is_main_outlet' => false]);
                                }

                                $modelClass::updateOrCreate(['uuid' => $uuid], $data);
                                
                            }
                        }
                        $results['success_count']++;
                    }

                    if ($deltaTotalSold != 0) {
                        $targetRecord = $existing ?? $modelClass::where('uuid', $uuid)->first();
                        
                        if ($targetRecord) {
                            $targetRecord->increment('total_sold', $deltaTotalSold);
                        }
                    }

                    if(!in_array($tableName, $changedTables)) {
                        $changedTables[] = $tableName;
                    }

                }, 3);
            } catch (\Exception $e) {
                $results['errors'][] = [
                    'table_name' => $item['table'],
                    'local_id'   => $item['local_id'],
                    'message'    => $e->getMessage()
                ];
            }
        }
        
        if($results['success_count'] > 0 && !empty($changedTables)) {
            $masterTables = [
                'tenants',
                'tenant_payment_methods',
                'outlets',
                'campaigns',
                'categories',
                'products',
                'product_variants',
                'outlet_inventories',
                'orders'
            ];
            
            $relevantChanges = array_intersect($changedTables, $masterTables);

            $tenantId = Auth::user()->tenant_id;
            broadcast(new DataBatchUpdated($tenantId, array_values($relevantChanges)))->toOthers();
        }
        return $results;
    }
    
    protected function tableHasColumn(string $table, string $column): bool
    {
        $cacheKey = "table_columns_{$table}";

        if (isset(static::$columnCache[$table])) {
            return in_array($column, static::$columnCache[$table]);
        }

        $columns = Cache::remember($cacheKey, now()->addDay(), function () use ($table) {
            return Schema::getColumnListing($table);
        });

        static::$columnCache[$table] = $columns;

        return in_array($column, $columns);
    }

    protected function usesSoftDeletes($modelClass): bool
    {
        if (isset(static::$softDeleteCache[$modelClass])) {
            return static::$softDeleteCache[$modelClass];
        }

        $uses = in_array(
            'Illuminate\Database\Eloquent\SoftDeletes',
            class_uses_recursive($modelClass)
        );

        static::$softDeleteCache[$modelClass] = $uses;
        return $uses;
    }

    private function handleDeltaStock($data)
    {
        $inventory = OutletInventory::withTrashed()
        ->lockForUpdate()
        ->where('uuid', '=', $data['outlet_inventory_id'])
        ->first();

        if (!$inventory) {
            throw new \Exception("inventory with id {$data['outlet_inventory_id']} is not found for stock synchronize");
        }

        if($inventory->stock_type === 'unlimited') return;

        if(isset($data['type']) && isset($data['stock_type']) && $data['stock_type'] === 'fixed') {
            $type = $data['type'];
            if (in_array($type, ['adjustment_in', 'adjustment_out'])) {
                $inventory->stock = $data['current_stock'];
                $inventory->updated_at = $data['created_at'];
                $inventory->save();
            } else if(in_array($type, ['sale', 'transfer_out'])) {
                $inventory->decrement('stock', $data['qty_change']);
            } else if(in_array($type, ['production', 'purchase', 'transfer_in'])) {
                $inventory->increment('stock', $data['qty_change']);
            }
        }

    }

    private function handleSoftDelete($modelClass, $uuid, $data)
    {
        $tableName = (new $modelClass())->getTable();
        $deletedAt = isset($data['deleted_at']) ? Carbon::parse($data['deleted_at']) : now();
        $query = $modelClass::withTrashed();
        
        if (empty($data) || (!isset($data['uuid']) && count($data) === 1)) {
            $query->where('uuid', $uuid);
        } else {
            foreach ($data as $key => $value) {
                if (in_array($key, ['is_active', 'is_delete_by_owner', 'deleted_at'])) continue;
                $query->where($key, $value);
            }
        }

        $records = $query->get();

        if ($records->isNotEmpty()) {
            foreach ($records as $record) {
                if (isset($data['is_delete_by_owner']) && $this->tableHasColumn($tableName, 'is_delete_by_owner')) {
                    $record->is_delete_by_owner = $data['is_delete_by_owner'];
                }

                if(isset($data['is_active']) && $this->tableHasColumn($tableName, 'is_active')) {
                    $record->is_active = $data['is_active'];
                }
                $record->deleted_at = $deletedAt;
                $record->save();
                $record->delete(); 
                $record->updateQuietly(['deleted_at' => $deletedAt]);
            }
        }
    }
}