<?php

namespace App\Traits;

use App\Models\MEDIA\Media;
use App\Services\StockLogsHandler;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Illuminate\Support\Facades\DB;
use Illuminate\Pagination\LengthAwarePaginator;
use Maatwebsite\Excel\Facades\Excel;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use ReflectionClass;
use ReflectionMethod;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Relations\Relation;

trait CrudTrait
{
    public function getData(Request $request)
    {
        $zid = uniqid('zid_', true);

        try {
            // 🔐 Permission Check
            if (!request()->user()->can($this->getPermissionName('view', $request))) {
                return $this->unauthorizedError($zid, 'view', $request);
            }

            // 🔎 Base Query
            $query = ($this->modelClass)::query();

            // Load relationships for roles
            if ($this->modelClass === \Spatie\Permission\Models\Role::class) {
                $query->with('permissions');
            }

            // ✔ Apply all generic filters (DB-level)
            $this->applySearch($query, $request);
            $this->applyFilters($query, $request);
            $this->applyMinMax($query, $request);
            $this->applyDateRange($query, $request);
            $this->applyStatus($query, $request);
            $this->applyLookup($query, $request);
            $this->applyGetById($query, $request);

            // 🧠 CLONE QUERY BEFORE PAGINATION (for fieldValues)
            $filtersQuery = clone $query;

            $this->applySorting($query, $request);
            $this->autoWithBelongsTo($query);

            // ✔ Special logic for operations
            if ($this->dataType === 'operation') {
                if ($request->operation) {
                    $query->where('docType', $request->operation);
                }

                $query->with([
                    'parent',
                    'children',
                    'warehouse',
                    'fromWarehouse',
                    'toWarehouse',
                    'payments'
                ]);
            }



            $pageSize = (int) ($request->pageSize ?? 10);
            $page = (int) ($request->page ?? 1);

            if ($pageSize <= 0) {

                $collection = $query->get();
                $total = $collection->count();

                $perPage = max($total, 1); // 🔒 NEVER ZERO

                $items = new LengthAwarePaginator(
                    $collection,
                    $total,
                    $perPage,
                    1
                );
            } else {

                $items = $query->paginate($pageSize, ['*'], 'page', $page);
            }


            // 🧩 Extract field values from FULL filtered dataset
            $filters = $this->extractFieldValues(
                $filtersQuery->get()
            );

            // 📦 Response
            return response()->json([
                'data' => $this->formatData($items, $request),
                'total' => $items->total(),
                'dataType' => $this->dataType,
                'totalPages' => $items->lastPage(),
                'currentPage' => $items->currentPage(),
                'pageSize' => $items->perPage(),
                'sortColumn' => $request->sortColumn ?? 'id',
                'sortDirection' => $request->sortDirection ?? 'desc',
                'fieldValues' => $filters,
                'statusCode' => 200,
                'success' => true,
                'zid' => $zid
            ]);
        } catch (\Throwable $e) {
            return $this->serverErrorWithZid($zid, $e);
        }
    }

    public function getPaymentStatusAttribute(): string
    {
        $totalTtc = (float) $this->total_ttc;
        $paidAmount = $this->payments->sum('amount');

        $dueDate = $this->due_date
            ? Carbon::parse($this->due_date)
            : Carbon::parse($this->invoiced_date)->addDays(30);

        if ($paidAmount <= 0) {
            return now()->gt($dueDate) ? 'overdue' : 'unpaid';
        }

        if ($paidAmount >= $totalTtc) {
            return 'paid';
        }

        return 'partiallypaid';
    }


    protected function formatData($items, Request $request)
    {
        $user = request()->user();
        $role = $user->roles->first();
        $model = $request->model;
        $roleColumns = json_decode($role->columns, true);
        $moduleData = collect($roleColumns)->firstWhere('module', $model);
        $allowedColumns = $moduleData['columns'] ?? [];
        if (empty($allowedColumns)) {
            return $items->items();
        }
        if ($this->dataType === 'operation') {
            return $items->map(function ($operation) use ($allowedColumns) {
                $filtered = collect($operation)->only($allowedColumns);
                //reducing the load by use doctype after this version
                if ($operation->relationLoaded('parent')) {
                    $filtered['parent'] = $operation->parent;
                }
                if ($operation->relationLoaded('invoice')) {
                    $filtered['invoice'] = $operation->invoice;
                }
                if ($operation->relationLoaded('pos')) {
                    $filtered['pos'] = $operation->pos;
                }
                if ($operation->relationLoaded('children')) {
                    $filtered['children'] = $operation->children;
                }
                if (
                    $operation->relationLoaded('payments') &&  in_array($operation->docType, ['purchaseorder', 'invoicesale'], true)
                ) {

                    $paidPayments = $operation->payments()
                        ->where('status', 'paid')
                        ->where('operationType', $operation->docType)
                        ->get();

                    $filtered['payments'] = $paidPayments;

                    $totalTtc   = (float) $operation->totalTtc;
                    $paidAmount = (float) $paidPayments->sum('amount');

                    $dueDate   = $operation->paymentDue ? Carbon::parse($operation->paymentDue) : null;
                    $isOverdue = $dueDate?->isPast() ?? false;

                    // 2. Determine paymentStatus (Bla return f west l-if)
                    if ($paidAmount >= $totalTtc && $paidAmount > 0) {
                        $filtered['paymentStatus'] = 'paid';
                    } elseif ($paidAmount > 0) {
                        $filtered['paymentStatus'] = 'partiallypaid';
                    } elseif ($isOverdue) {
                        $filtered['paymentStatus'] = 'overdue';
                        $filtered['lateDays']      = $dueDate->diffInDays(now());
                    } else {
                        $filtered['paymentStatus'] = 'unpaid';
                    }
                }
                if ($operation === 'exitnote' || $operation === 'entrynote' || $operation === 'transfernote' || $operation === 'adjustmentnote') {
                    if ($operation->relationLoaded('warehouse')) {
                        $filtered['warehouse'] = $operation->warehouse;
                    }
                    if ($operation->relationLoaded('fromWarehouse')) {
                        $filtered['fromWarehouse'] = $operation->fromWarehouse;
                    }
                    if ($operation->relationLoaded('toWarehouse')) {
                        $filtered['toWarehouse'] = $operation->toWarehouse;
                    }
                }

                return $filtered;
            })->toArray();
        }
        if ($this->dataType === 'product') {
            $items->load('stocknapshot');
            // 👉 SERVICE PRODUCT

            return $items->map(function ($product) use ($allowedColumns) {
                if ($product->type === 'service') {
                    $filtered['stock'] = null;
                    $filtered['stockStatus'] = 'service';
                    $filtered['lastPrice'] = null;
                    $filtered['lastCost'] = null;

                    return $filtered;
                }
                $filtered = collect($product->only($allowedColumns));
                $filtered['stock'] = $product->stocknapshot?->stock ?? 0;
                $filtered['stockStatus'] = '';
                if ($product->stocknapshot?->stock > $product->minStock) {
                    $filtered['stockStatus'] = 'inStock';
                }
                if ($product->stocknapshot?->stock === $product->minStock) {
                    $filtered['stockStatus'] = 'alertStock';
                }
                if ($product->stocknapshot?->stock < $product->minStock) {
                    $filtered['stockStatus'] = 'OutOfStock';
                }
                $filtered['lastPrice'] = $product->stocknapshot?->lastPrice ?? 0;
                $filtered['lastCost'] = $product->stocknapshot?->lastCost ?? 0;

                return $filtered;
            })->values()->toArray();
        }
        if ($model === 'payment') {
            return $items->map(function ($payment) use ($allowedColumns) {
                $filtered = [];

                // Copie les colonnes autorisées
                foreach ($allowedColumns as $col) {
                    if (isset($payment[$col])) {
                        $filtered[$col] = $payment[$col];
                    }
                }

                // Ajouter user si relation chargée
                if ($payment->relationLoaded('user')) {
                    $filtered['user'] = $payment->user;
                }

                // Colonne calculée "signe"
                switch ($payment['operationType']) {
                    case 'invoicesale':
                    case 'possale':
                        $filtered['direction'] = 'received';
                        break;
                    case 'purchaseorder':
                    case 'expense':
                        $filtered['direction'] = 'paid';
                        break;
                    default:
                        $filtered['direction'] = null;
                        break;
                }

                return $filtered;
            })->values()->toArray();
        }
        if ($model === 'pos') {
            return $items->map(function ($pos) use ($allowedColumns) {
                $filtered = collect($pos)->only($allowedColumns)->toArray();
                if ($pos->relationLoaded('owner')) {
                    $filtered['owner'] = $pos->owner;
                }

                if ($pos->relationLoaded('cashier')) {
                    $filtered['cashier'] = $pos->cashier;
                }

                if ($pos->relationLoaded('operations')) {
                    // Use the in-memory collection if loaded
                    $posOperations = $pos->operations->where('docType', 'possale');
                    $sumTtc = $posOperations->sum('totalTtc');

                    $filtered['currentBalance'] = (float)$sumTtc + (float)$filtered['openingBalance'];
                } else {
                    // Fallback to querying the database
                    $sumTtc = $pos->operations()
                        ->where('docType', 'possale')
                        ->sum('totalTtc');

                    $filtered['currentBalance'] = (float)$sumTtc + (float)$filtered['openingBalance'];
                }

                // Optionally, sum all totalTtc for quick reference


                return $filtered;
            })->values()->toArray();
        }


        if ($model === 'user') {

            $items->load('roles');

            return $items->map(function ($user) use ($allowedColumns) {

                $filtered = collect($user->toArray())
                    ->only($allowedColumns)
                    ->toArray();

                $filtered['roles'] = $user->roles
                    ->pluck('name')
                    ->values()
                    ->toArray();

                return $filtered;
            })->values()->toArray();
        }


        $filteredItems = $items->map(function ($item) use ($allowedColumns) {
            return collect($item)->only($allowedColumns);
        });
        return $filteredItems->toArray();
    }

    protected function autoWithBelongsTo($query)
    {
        $model = $query->getModel();
        $with = [];

        $reflection = new ReflectionClass($model);

        foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
            // skip inherited methods
            if ($method->class !== get_class($model)) {
                continue;
            }

            // must have no parameters
            if ($method->getNumberOfParameters() > 0) {
                continue;
            }

            try {
                $relation = $method->invoke($model);

                if ($relation instanceof BelongsTo) {
                    $with[] = $method->getName();
                }
            } catch (\Throwable $e) {
                // ignore non-relation methods
            }
        }

        if (!empty($with)) {
            $query->with($with);
        }
    }

    public function storeData(Request $request)
    {
        $zid = uniqid('zid_', true);
        try {
            // 🔐 Check permission (create)
            if (!request()->user()->can($this->getPermissionName('create', $request))) {
                return $this->unauthorizedError($zid, 'create', $request);
            }

            // ✅ Accept only JSON input
            if (!$request->isJson()) {
                return response()->json([
                    'success' => false,
                    'message' => 'Only JSON requests are allowed.',
                    'zid' => $zid,
                ], 415);  // Unsupported Media Type
            }

            $data = $request->json()->all();

            // 📝 Create new item
            $item = ($this->modelClass)::create($data);

            if ($this->dataType === 'operation') {
                $this->createOperationCounter($request);
            }

            return response()->json([
                'data' => $item,
                'success' => true,
                'statusCode' => 200,
                'zid' => $zid,
            ]);
        } catch (\Throwable $e) {
            return $this->serverErrorWithZid($zid, $e);
        }
    }

    public function bulkStore(Request $request)
    {
        $zid = uniqid('zid_', true);
        try {
            // Check permission (create)
            if (!request()->user()->can($this->getPermissionName('create', $request))) {
                return $this->unauthorizedError($zid, '', $request);
            }
            $items = ($this->modelClass)::insert($request->all());
            return response()->json([
                'data' => $items,
                'success' => true,
                'statusCode' => 200,
                'zid' => $zid
            ]);
        } catch (\Throwable $e) {
            return $this->serverErrorWithZid($zid, $e);
        }
    }

    public function updateData(Request $request)
    {
        $zid = uniqid('zid_', true);

        try {
            // 🔐 Check permission (update)
            if (!request()->user()->can($this->getPermissionName('update', $request))) {
                return $this->unauthorizedError($zid, 'update', $request);
            }

            // ✅ Accept only JSON input
            if (!$request->isJson()) {
                return response()->json([
                    'success' => false,
                    'message' => 'Only JSON requests are allowed.',
                    'zid' => $zid,
                ], 415);
            }

            $data = $request->json()->all();
            // if model is setting

            if (empty($data['id'])) {
                return response()->json([
                    'success' => false,
                    'message' => 'Missing record ID.',
                    'zid' => $zid,
                ], 400);
            }

            $item = ($this->modelClass)::find($data['id']);
            $oldStatus = $item->status;
            if (!$item) {
                return $this->notFound($zid);
            }

            if (isset($data['items']) && $item->items !== $data['items']) {
                $data['status'] = 'draft';
            }

            // 💾 Update record
            $item->update($data);

            if ($this->dataType === 'operation') {
                StockLogsHandler::handleStatusChange($item, $oldStatus, $item['status']);
            }

            return response()->json([
                'data' => $item,
                'success' => true,
                'statusCode' => 200,
                'zid' => $zid,
            ]);
        } catch (ModelNotFoundException $e) {
            return $this->notFound($zid);
        } catch (\Throwable $e) {
            return $this->serverErrorWithZid($zid, $e);
        }
    }

    public function multiUpdate(Request $request)
    {
        $zid = uniqid('zid_', true);

        try {
            // ✅ Global permission (ila maشي operation)
            if ($this->dataType !== 'operation') {

                if (!request()->user()->can(
                    $this->getPermissionName('update', $request)
                )) {
                    return $this->unauthorizedError($zid, '', $request);
                }
            }

            $items = $request->all();

            if (!is_array($items) || empty($items)) {
                return response()->json([
                    'success' => false,
                    'message' => 'No data provided',
                    'zid' => $zid
                ], 400);
            }

            $updated = [];

            foreach ($items as $item) {

                if (!isset($item['id'])) {
                    continue;
                }

                $model = ($this->modelClass)::find($item['id']);

                if (!$model) {
                    continue;
                }

                // ✅ Operation permission by docType
                if ($this->dataType === 'operation') {

                    $permission = $model->docType . '-update';

                    if (!request()->user()->can($permission)) {
                        return response()->json([
                            'success' => false,
                            'message' => 'Missing permission: ' . $permission,
                            'requiredPermission' => $permission,
                            'zid' => $zid,
                        ], 403);
                    }
                }

                $oldStatus = $model->status;

                $model->update($item);

                // ✅ Handle stock change only for operation
                if ($this->dataType === 'operation') {
                    StockLogsHandler::handleStatusChange(
                        $model,
                        $oldStatus,
                        $model->status
                    );
                }

                $updated[] = $model;
            }

            return response()->json([
                'data' => $updated,
                'success' => true,
                'statusCode' => 200,
                'zid' => $zid
            ]);
        } catch (\Throwable $e) {

            Log::error('multiUpdate error', [
                'message' => $e->getMessage(),
                'zid' => $zid
            ]);

            return $this->serverErrorWithZid($zid, $e);
        }
    }


    public function destroyData(Request $request)
    {
        $zid = uniqid('zid_', true);
        try {
            // Check permission (delete)
            if (!request()->user()->can($this->getPermissionName('delete', $request))) {
                return $this->unauthorizedError($zid, 'delete', $request);
            }
            $item = ($this->modelClass)::findOrFail($request->id);
            $id = $item->id;
            $item->delete();
            return response()->json([
                'data' => [
                    'id' => $id
                ],
                'success' => true,
                'statusCode' => 200,
                'zid' => $zid,
            ]);
        } catch (ModelNotFoundException $e) {
            return $this->notFound($zid);
        } catch (\Throwable $e) {
            return $this->serverErrorWithZid($zid, $e);
        }
    }

    public function bulkDelete(Request $request)
    {
        $zid = uniqid('zid_', true);
        try {
            // Check permission (delete)
            if (!request()->user()->can($this->getPermissionName('delete', $request))) {
                return $this->unauthorizedError($zid, 'delete', $request);
            }
            if (!is_array($request->ids) || empty($request->ids)) {
                return response()->json([
                    'success' => false,
                    'statusCode' => 400,
                    'zid' => $zid,
                    'message' => 'حقل ids مفقود أو غير صالح'
                ]);
            }
            $items = ($this->modelClass)::whereIn('id', $request->ids)->get();
            // استخراج ids
            $ids = $items->pluck('id')
                ->map(fn($id) => ['id' => $id])
                ->values();

            foreach ($items as $item) {
                $item->delete();
            }

            return response()->json([
                'data' => $ids,
                'success' => true,
                'statusCode' => 200,
                'zid' => $zid,
            ]);
        } catch (\Throwable $e) {
            return $this->serverErrorWithZid($zid, $e);
        }
    }


    public function cloneData(Request $request)
    {
        $zid = uniqid('zid_', true);
        try {
            $item = ($this->modelClass)::findOrFail($request->id);
            $clonedItem = $item->replicate();
            $tableName = $item->getTable();
            $indexes = Schema::getIndexes($tableName);
            foreach ($indexes as $index) {

                if ($index['unique'] && !$index['primary']) {

                    foreach ($index['columns'] as $column) {

                        $originalValue = $clonedItem->{$column};

                        if (is_string($originalValue) && !empty($originalValue)) {

                            // Remove old (number) if exists
                            $baseValue = preg_replace('/\s\(\d+\)$/', '', $originalValue);

                            $newValue = $baseValue;
                            $counter = 1;

                            // Loop until unique value found
                            while (
                                ($this->modelClass)::withTrashed()
                                ->where($column, $newValue)
                                ->exists()
                            ) {
                                $newValue = $baseValue . ' (' . $counter . ')';
                                $counter++;
                            }

                            $clonedItem->{$column} = $newValue;
                        }
                    }
                }
            }
            $clonedItem->save();
            return response()->json([
                'data' => $clonedItem,
                'success' => true,
                'zid' => $zid
            ]);
        } catch (\Throwable $e) {
            return $this->serverErrorWithZid($zid, $e);
        }
    }
    public function exportData(Request $request)
    {
        $zid = uniqid('zid_', true);
        try {
            // Check permission (export)
            if (!request()->user()->can($this->getPermissionName('export', $request))) {
                return $this->unauthorizedError($zid, 'export', $request);
            }
            $query = ($this->modelClass)::query();

            $this->applySearch($query, $request);
            $this->applyFilters($query, $request);
            $this->applyMinMax($query, $request);
            $this->applyDateRange($query, $request);
            $this->applyStatus($query, $request);
            $this->applySorting($query, $request);

            $items = $query->get();
            return response()->json([
                'data' => $items,
                'success' => true,
                'statusCode' => 200,
                'zid' => $zid
            ]);
        } catch (\Throwable $e) {
            return $this->serverErrorWithZid($zid, $e);
        }
    }

    public function importData(Request $request)
    {
        $zid = uniqid('zid_', true);
        try {
            if (! $request->user()->can($this->getPermissionName('import', $request))) {
                return $this->unauthorizedError($zid, 'import', $request);
            }

            if (! $request->hasFile('file')) {
                return response()->json([
                    'success' => false,
                    'message' => 'Import file is missing',
                    'zid' => $zid
                ], 400);
            }

            $file = $request->file('file');
            $extension = strtolower($file->getClientOriginalExtension());

            if ($extension === 'csv') {
                $rows = array_map('str_getcsv', file($file->getRealPath()));
            } elseif (in_array($extension, ['xlsx', 'xls'])) {
                $rows = Excel::toArray([], $file)[0];
            } else {
                return response()->json([
                    'success' => false,
                    'message' => 'Unsupported file type',
                    'zid' => $zid
                ], 415);
            }

            if (count($rows) < 2) {
                return response()->json([
                    'success' => false,
                    'message' => 'Empty file',
                    'zid' => $zid
                ], 400);
            }

            $headers = array_map('trim', array_shift($rows));

            $model     = new $this->modelClass();
            $fillable  = $model->getFillable();
            $casts     = $model->getCasts();

            $inserted = [];
            $errors   = [];

            DB::transaction(function () use (
                $rows,
                $headers,
                $fillable,
                $casts,
                &$inserted,
                &$errors
            ) {
                foreach ($rows as $index => $row) {

                    $rowData = array_combine($headers, $row);
                    $data    = [];

                    foreach ($rowData as $column => $value) {

                        if ($value === null || $value === '') {
                            continue;
                        }
                        if (
                            isset($casts[$column]) &&
                            in_array($casts[$column], ['array', 'json'])
                        ) {
                            $table = Str::plural($column);

                            if (!Schema::hasTable($table)) {
                                continue;
                            }

                            $item = DB::table($table)
                                ->where('name', $value)
                                ->first();

                            if (!$item) {
                                $id = DB::table($table)->insertGetId([
                                    'uid'        => Str::uuid(),
                                    'name'       => $value,
                                    'created_at' => now(),
                                    'updated_at' => now(),
                                ]);

                                $item = DB::table($table)->where('id', $id)->first();
                            }

                            $data[$column] = [
                                'id'   => $item->id,
                                'name' => $item->name,
                            ];

                            continue;
                        }


                        if (in_array($column, $fillable)) {
                            $data[$column] = $value;
                        }
                    }

                    if (empty($data)) {
                        $errors[] = [
                            'row'   => $index + 2,
                            'error' => 'No valid data'
                        ];
                        continue;
                    }

                    try {
                        $inserted[] = $this->modelClass::create($data);
                    } catch (\Throwable $e) {
                        $errors[] = [
                            'row'   => $index + 2,
                            'error' => $e->getMessage()
                        ];
                    }
                }
            });

            return response()->json([
                'success'  => true,
                'inserted' => count($inserted),
                'errors'   => $errors,
                'zid'      => $zid
            ]);
        } catch (\Throwable $e) {
            return $this->serverErrorWithZid($zid, $e);
        }
    }

    public function exampleCsv()
    {
        $zid = uniqid('zid_', true);
        try {
            $model = new $this->modelClass();
            $fillable = $model->getFillable();

            $csvContent = implode(',', $fillable) . "\n";

            return response($csvContent, 200, [
                'Content-Type' => 'text/csv',
                'Content-Disposition' => 'attachment; filename="example_' . strtolower(class_basename($this->modelClass)) . '.csv"',
            ]);
        } catch (\Throwable $e) {
            return $this->serverErrorWithZid($zid, $e);
        }
    }

    public function exampleXlsx()
    {
        $zid = uniqid('zid_', true);

        try {
            $model = new $this->modelClass();
            $fillable = $model->getFillable();

            // headers + empty row
            $rows = [
                $fillable,
                array_fill(0, count($fillable), null),
            ];

            return response()->streamDownload(function () use ($rows) {
                $handle = fopen('php://output', 'w');

                foreach ($rows as $row) {
                    fputcsv($handle, $row);
                }

                fclose($handle);
            }, 'example_' . strtolower(class_basename($this->modelClass)) . '.csv', [
                'Content-Type' => 'text/csv',
            ]);
        } catch (\Throwable $e) {
            return $this->serverErrorWithZid($zid, $e);
        }
    }


    public function uploadFile(Request $request)
    {
        $zid = uniqid('zid_', true);

        try {
            $uploads = [];
            $modelName = strtolower(class_basename($this->modelClass ?? 'general'));

            // Get all files from the request
            $files = $request->file('files');

            if (empty($files)) {
                return response()->json([
                    'success' => false,
                    'statusCode' => 400,
                    'zid' => $zid,
                    'errorMessage' => 'No files uploaded',
                ], 400);
            }

            foreach ($files as $key => $file) {
                if ($file instanceof \Illuminate\Http\UploadedFile && $file->isValid()) {
                    $folder = $modelName ?? 'general';

                    // store the file
                    $storedPath = $file->store("uploads/{$folder}", 'public');

                    // generate public URL
                    $url = asset("storage/{$storedPath}");

                    $mediaData = [
                        'name' => $file->getClientOriginalName(),
                        'path' => $url,  // store full URL
                        'mime_type' => $file->getMimeType(),
                        'size' => $file->getSize(),
                        'disk' => 'public',
                        'type' => $this->detectMediaType($file->getMimeType()),
                    ];

                    $media = Media::create($mediaData);

                    $uploads[$key] = [
                        'id' => $media->id,
                        'name' => $media->name,
                        'url' => $media->path,  // already full URL
                        'mime_type' => $media->mime_type,
                        'size' => $media->size,
                        'type' => $media->type,
                    ];
                }
            }

            return response()->json([
                'success' => true,
                'statusCode' => 200,
                'uploads' => $uploads,
                'zid' => $zid,
                'errorMessage' => 'files uploaded',
            ], 200);
        } catch (\Throwable $e) {
            return response()->json([
                'success' => false,
                'statusCode' => 500,
                'zid' => $zid,
                'errorMessage' => $e->getMessage(),
            ], 500);
        }
    }

    private function detectMediaType($mimeType)
    {
        if (str_starts_with($mimeType, 'image/')) {
            return 'image';
        } elseif (str_starts_with($mimeType, 'video/')) {
            return 'video';
        } elseif (str_starts_with($mimeType, 'audio/')) {
            return 'audio';
        } elseif ($mimeType === 'application/pdf') {
            return 'document';
        } else {
            return 'file';
        }
    }

    public function updateFile(Request $request)
    {
        $zid = uniqid('zid_', true);

        try {
            $uploads = [];
            $modelName = strtolower(class_basename($this->modelClass));
            foreach ($request->files as $key => $file) {
                if ($file->isValid()) {
                    // Determine folder name based on data type or fallback
                    $folder = $modelName ?? 'general';
                    $path = $file->store("uploads/{$folder}", 'public');

                    // Gather metadata
                    $mediaData = [
                        'name' => $file->getClientOriginalName(),
                        'path' => $path,
                        'mime_type' => $file->getMimeType(),
                        'size' => $file->getSize(),
                        'disk' => 'public',
                        'type' => $this->detectMediaType($file->getMimeType()),
                    ];

                    // Store in database
                    $media = Media::create($mediaData);

                    // Add to response
                    $uploads[$key] = [
                        'id' => $media->id,
                        'name' => $media->name,
                        'url' => asset("storage/{$path}"),
                        'mime_type' => $media->mime_type,
                        'size' => $media->size,
                        'type' => $media->type,
                    ];
                }
            }

            return response()->json([
                'success' => true,
                'uploads' => $uploads,
                'zid' => $zid,
            ]);
        } catch (\Throwable $e) {
            return $this->serverErrorWithZid($zid, $e);
        }
    }

    public function deleteMedia(Request $request)
    {
        $zid = uniqid('zid_', true);

        try {
            // 🧾 Validate input
            $mediaId = $request->input('id');
            if (!$mediaId) {
                return response()->json([
                    'success' => false,
                    'message' => 'Missing media ID.',
                    'zid' => $zid,
                ], 400);
            }

            // 🔍 Find media record
            $media = Media::find($mediaId);
            if (!$media) {
                return response()->json([
                    'success' => false,
                    'message' => "Media not found for ID {$mediaId}.",
                    'zid' => $zid,
                ], 404);
            }

            if ($media->path) {
                // 🔹 Normalize slashes (Windows/Linux compatibility)
                $path = str_replace('\\', '/', $media->path);

                // 🔹 Extract part after "storage/"
                $relativePath = preg_replace('#.*storage/(.+)$#', '$1', $path);

                // 🧹 Just in case there's no "storage/" in the path (e.g. stored already clean)
                if ($relativePath === $path) {
                    $relativePath = ltrim($path, '/');
                }

                // 🔍 Delete file using Laravel Storage (points to storage/app/public)
                if (Storage::disk($media->disk)->exists($relativePath)) {
                    Storage::disk($media->disk)->delete($relativePath);
                }
            }

            // 🧼 Delete record from database
            $media->delete();

            return response()->json([
                'success' => true,
                'message' => "Media ID {$mediaId} deleted successfully.",
                'zid' => $zid,
            ]);
        } catch (\Throwable $e) {
            return response()->json([
                'success' => false,
                'message' => 'Server error while deleting media.',
                'zid' => $zid,
            ], 500);
        }
    }

    public function downloadFile(Request $request)
    {
        $zid = uniqid('zid_', true);
        try {
            // Check permission (download)
            if (!request()->user()->can($this->getPermissionName('download', $request))) {
                return $this->unauthorizedError($zid, 'download', $request);
            }
            $filePath = $request->input('filePath');
            if (!$filePath) {
                return response()->json([
                    'success' => false,
                    'statusCode' => 400,
                    'zid' => $zid,
                    'message' => 'مسار الملف مفقود'
                ]);
            }
            return response()->download(storage_path("app/public/$filePath"));
        } catch (\Throwable $e) {
            return $this->serverErrorWithZid($zid, $e);
        }
    }

    public function getDataAsArray()
    {
        $zid = uniqid('zid_', true);

        try {
            $entries = ($this->modelClass)::pluck('name')->toArray();

            return response()->json([
                'success' => true,
                'statusCode' => 200,
                'data' => $entries,
                'zid' => $zid
            ]);
        } catch (\Throwable $e) {
            return $this->serverErrorWithZid($zid, $e);
        }
    }

    public function getDataOfModelAsArrayByColumns(Request $request)
    {
        $zid = uniqid('zid_', true);

        try {
            $entries = ($this->modelClass)::pluck('name')->toArray();

            return response()->json([
                'success' => true,
                'statusCode' => 200,
                'data' => $entries,
                'zid' => $zid
            ]);
        } catch (\Throwable $e) {
            return $this->serverErrorWithZid($zid, $e);
        }
    }

    /**
     * ========== Private Helpers ==========
     */
    protected function findModelClass(string $modelName): ?string
    {
        $basePath = app_path('Models');
        $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basePath));

        foreach ($iterator as $file) {
            if ($file->isFile() && $file->getExtension() === 'php') {
                $className = 'App\\Models\\' . str_replace(
                    ['/', '.php'],
                    ['\\', ''],
                    Str::after($file->getPathname(), $basePath . DIRECTORY_SEPARATOR)
                );

                if (class_exists($className) && Str::lower(class_basename($className)) === Str::lower($modelName)) {
                    return $className;
                }
            }
        }

        return null;
    }

    protected function applySearch($query, Request $request)
    {
        if ($request->filled('searchValueIn')) {

            $searchIn = is_string($request->searchValueIn)
                ? json_decode($request->searchValueIn, true)
                : $request->searchValueIn;

            $fields = $searchIn['fields'] ?? [];
            $value  = $searchIn['value'] ?? null;

            if (!empty($fields) && !empty($value)) {

                $query->where(function ($q) use ($fields, $value) {
                    foreach ($fields as $field) {
                        $q->orWhere($field, 'LIKE', '%' . $value . '%');
                    }
                });

                return;
            }
        }

        if ($request->filled('searchValueInColumn')) {

            $search = is_string($request->searchValueInColumn)
                ? json_decode($request->searchValueInColumn, true)
                : $request->searchValueInColumn;

            $field = $search['fields'] ?? null;
            $value = $search['value'] ?? null;

            if ($field && $value !== null) {
                $query->where($field, 'LIKE', '%' . $value . '%');
                return;
            }
        }


        if ($request->filled('searchValuEqual')) {

            $search = is_string($request->searchValuEqual)
                ? json_decode($request->searchValuEqual, true)
                : $request->searchValuEqual;

            $fields = $search['fields'] ?? [];
            $value  = $search['value'] ?? null;

            if (!empty($fields) && $value !== null) {

                // 🔐 حماية من column injection
                $table = $query->getModel()->getTable();
                $columns = Schema::getColumnListing($table);
                $fields = array_intersect($fields, $columns);

                if (!empty($fields)) {
                    $query->where(function ($q) use ($fields, $value) {
                        foreach ($fields as $field) {
                            $q->orWhere($field, '=', $value);
                        }
                    });

                    return;
                }
            }
        }



        if ($request->searchValue) {

            $cols = $this->getRoleColumnsByModule($request->model);

            if (!is_array($cols) || empty($cols)) {
                return response()->json([
                    'success' => false,
                    'statusCode' => 403,
                    'message' => 'Forbidden: You are not allowed to search in this module.',
                ], 403)->throwResponse();
            }

            $query->where(function ($q) use ($cols, $request) {
                foreach ($cols as $col) {
                    $q->orWhere($col, 'LIKE', '%' . $request->searchValue . '%');
                }
            });
        }
    }



    protected function getRoleColumnsByModule(string $module): array
    {
        $user = request()->user();
        $role = $user->roles()->first();
        if (!$role || !$role->columns) {
            return [];
        }
        $columnsArray = json_decode($role->columns, true) ?? [];
        $moduleData = collect($columnsArray)
            ->firstWhere('module', $module);
        return $moduleData['columns'] ?? [];
    }



    protected function applyFilters($query, Request $request)
    {
        if (!$request->filled('filter')) {
            return;
        }

        $filters = is_string($request->filter)
            ? json_decode($request->filter, true)
            : $request->filter;

        $model = $query->getModel(); // ✅ real model instance

        foreach ($filters ?? [] as $filter) {

            if (!isset($filter['field'], $filter['value'])) {
                continue;
            }

            $field = $filter['field'];
            $value = $filter['value'];

            // split csv
            if (is_string($value) && str_contains($value, ',')) {
                $value = array_map('trim', explode(',', $value));
            }

            /*
        |--------------------------------------------------------------------------
        | 🔥 RELATION FILTER (roles, permissions, etc)
        |--------------------------------------------------------------------------
        */

            if (method_exists($model, $field)) {
                try {
                    $relation = $model->{$field}();

                    if ($relation instanceof Relation) {

                        $values = is_array($value) ? $value : [$value];

                        $query->whereHas($field, function ($q) use ($values) {
                            $q->whereIn('name', $values);
                        });

                        $query->with($field);

                        continue; // 🚨 stop here — prevent column filter
                    }
                } catch (\Throwable $e) {
                    // not a relation — continue
                }
            }

            /*
        |--------------------------------------------------------------------------
        | JSON cast fields
        |--------------------------------------------------------------------------
        */

            if ($model->hasCast($field, ['object', 'json', 'array'])) {

                if (is_array($value)) {
                    $query->where(function ($q) use ($field, $value) {
                        foreach ($value as $v) {
                            $q->orWhere($field . '->name', $v);
                        }
                    });
                } else {
                    $query->where($field . '->name', $value);
                }

                continue;
            }

            /*
        |--------------------------------------------------------------------------
        | Normal filters
        |--------------------------------------------------------------------------
        */

            if (is_array($value)) {
                $query->whereIn($field, $value);
            } elseif (Str::contains(strtolower($field), 'date')) {
                $query->whereDate($field, $value);
            } else {
                $query->where($field, $value);
            }
        }
    }




    protected function applyMinMax($query, Request $request)
    {
        if ($request->minMax) {
            foreach ($request->minMax as $mm) {
                $query->whereBetween($mm['field'], [$mm['min'], $mm['max']]);
            }
        }
    }

    protected function applyDateRange($query, Request $request)
    {
        $raw = $request->input('dateRange');

        if (empty($raw))
            return;

        // Decode JSON if string
        $dateRanges = is_string($raw) ? json_decode($raw, true) : $raw;
        if (!is_array($dateRanges))
            return;

        foreach ($dateRanges as $range) {
            if (isset($range['field'], $range['startDate'], $range['endDate'])) {
                // Convert timestamp (milliseconds) to Carbon dates
                try {
                    $start = \Carbon\Carbon::createFromTimestampMs($range['startDate'])->startOfDay();
                    $end = \Carbon\Carbon::createFromTimestampMs($range['endDate'])->endOfDay();
                } catch (\Exception $e) {
                    Log::error('Invalid timestamp in date range filter', [
                        'error' => $e->getMessage(),
                        'range' => $range,
                    ]);
                    continue;
                }

                $query->whereBetween($range['field'], [$start, $end]);
            }
        }
    }

    protected function applyStatus($query, Request $request)
    {
        if ($request->status) {
            $query->where('status', $request->status);
        }
    }

    protected function applySorting($query, Request $request)
    {
        //need fixing after
        if ($request->sortColumn === 'stock') {
            $col = 'id';
        } else {
            $col = $request->sortColumn ?? 'id';
        }

        $dir = $request->sortDirection ?? 'desc';
        $query->orderBy($col, $dir);
    }

    private function buildAggregations(Request $request, $query)
    {
        $aggregations = [];
        $fields = $request->input('aggregations', []);  // string[]

        foreach ($fields as $field) {
            $clone = clone $query;  // باش ناخد نفس الفلاتر

            $aggregations[] = [
                'field' => $field,
                'count' => (clone $clone)->whereNotNull($field)->count(),
                'sum' => (clone $clone)->sum($field),
                'avg' => (clone $clone)->avg($field),
                'min' => (clone $clone)->min($field),
                'max' => (clone $clone)->max($field),
            ];
        }

        return $aggregations;
    }

    protected function applyLookup($query, Request $request)
    {
        if ($request->filled('lookupModel') && $request->filled('lookUpid')) {
            $lookupModel = $request->input('lookupModel');  // e.g., "employee"
            $lookupId = $request->input('lookUpid');  // e.g., 1

            // Use backticks for column name to avoid issues with reserved words
            $query->whereRaw("JSON_EXTRACT(`$lookupModel`, '\$.id') = ?", [$lookupId]);
        }
    }

    protected function applyGetById($query, Request $request)
    {
        if ($request->filled('id')) {
            $id = $request->input('id');
            $query->where('id', $id);
        }
    }



    protected function getVisibleFieldsFromModel(string $modelClass): array
    {
        $model = new $modelClass;

        $table = $model->getTable();
        $allFields = DB::getSchemaBuilder()->getColumnListing($table);

        $hiddenFields = array_merge($model->getHidden(), ['id', 'uid']);

        return array_values(array_diff($allFields, $hiddenFields));
    }

    protected function extractFieldValues($items): array
    {
        $fields = $this->getVisibleFieldsFromModel($this->modelClass);
        $result = [];

        foreach ($items as $item) {
            foreach ($fields as $field) {
                $value = $item[$field] ?? null;
                $result[$field][] = $value;
            }
        }

        foreach ($result as $field => $values) {
            $counts = [];
            foreach ($values as $value) {
                if (is_null($value)) {
                    $key = 'null';
                } elseif (is_scalar($value)) {
                    $key = (string) $value;
                } else {
                    $key = json_encode($value);
                }
                $counts[$key] = ($counts[$key] ?? 0) + 1;
            }
            $result[$field] = $counts;
        }

        return $result;
    }

    public function getModels(?string $basePath = null): array
    {
        $path = $basePath ?? app_path('Models');

        if (!File::exists($path)) {
            return [];
        }

        $files = File::allFiles($path);

        $models = collect($files)->map(function ($file) use ($path) {
            // Relative path
            $relativePath = Str::after($file->getPathname(), $path . DIRECTORY_SEPARATOR);

            $parts = explode(DIRECTORY_SEPARATOR, $relativePath);

            $module = strtolower(pathinfo(end($parts), PATHINFO_FILENAME));

            $group = count($parts) > 1
                ? strtolower(implode('/', array_slice($parts, 0, -1)))
                : 'default';

            $namespace = $this->getModelNamespace($relativePath);

            $columns = class_exists($namespace)
                ? $this->getFillableColumns($namespace)
                : [];

            return [
                'group' => $group,
                'module' => $module,
                'columns' => $columns,
            ];
        });

        // regroup by group name
        return $models->groupBy('group')->map(function ($items, $group) {
            return [
                'group' => $group,
                'tuple' => $items->map(function ($item) {
                    return [
                        'module' => $item['module'],
                        'columns' => $item['columns']
                    ];
                })->values()->toArray(),
            ];
        })->values()->toArray();
    }

    protected function getModelNamespace(string $relativePath): string
    {
        $classPath = str_replace(['/', '.php'], ['\\', ''], $relativePath);
        return "App\\Models\\{$classPath}";
    }

    protected function getFillableColumns(string $modelClass): array
    {
        try {
            if (!class_exists($modelClass)) {
                return [];
            }

            $model = new $modelClass();

            $fillable = $model->getFillable();

            if (method_exists($model, 'usesTimestamps') && $model->usesTimestamps()) {
                foreach (['created_at', 'updated_at'] as $col) {
                    if (!in_array($col, $fillable)) {
                        $fillable[] = $col;
                    }
                }
            }

            foreach (['id', 'deleted_at'] as $col) {
                try {
                    if (
                        !in_array($col, $fillable) &&
                        method_exists($model, 'getTable') &&
                        Schema::hasColumn($model->getTable(), $col)
                    ) {
                        $fillable[] = $col;
                    }
                } catch (\Throwable $e) {
                    Log::warning("Skipped {$col} for {$modelClass}: " . $e->getMessage());
                }
            }

            // 🔹 Make array sequential
            return array_values(array_unique($fillable));
        } catch (\Throwable $e) {
            Log::error("Model inspection failed for {$modelClass}: " . $e->getMessage());
            return [];
        }
    }

    protected function serverErrorWithZid($zid, $e)
    {
        return response()->json([
            'success' => false,
            'statusCode' => 500,
            'zid' => $zid,
            'errorLine' => $e->getLine(),
            'errorMessage' => $e->getMessage(),
            'errorFile' => $e->getFile(),
        ], 500);
    }

    protected function serverError($e)
    {
        return $this->serverErrorWithZid(uniqid('zid_', true), $e);
    }

    protected function notFound($zid)
    {
        return response()->json([
            'success' => false,
            'statusCode' => 404,
            'zid' => $zid
        ], 404);
    }

    protected function getPermissionName(string $action, Request $request): string
    {
        if (strtolower(class_basename($this->modelClass)) === 'operation') {
            $operationName = $request->operation ?? $request->docType;
            return "$operationName-$action";
        }
        $modelName = strtolower(class_basename($this->modelClass));
        return "$modelName-$action";
    }

    protected function unauthorizedError($zid, $action, Request $request)
    {
        return response()->json([
            'success' => false,
            'statusCode' => 403,
            'zid' => $zid,
            'message' => 'Missing permission: ' . $this->getPermissionName($action, $request),
            'requiredPermission' => $this->getPermissionName($action, $request),
        ], 403);
    }

    protected function handleFileUploads(Request $request, $existingItem = null): array
    {
        $uploads = [];
        $modelName = strtolower(class_basename($this->modelClass));  // ex: Employee -> "employee"

        foreach ($request->allFiles() as $field => $files) {
            // single file -> normalize to array
            $files = is_array($files) ? $files : [$files];

            $storedPaths = [];

            foreach ($files as $file) {
                if (!$file instanceof UploadedFile) {
                    // skip non-file values (ex: empty or data URL)
                    continue;
                }

                if (!$file->isValid()) {
                    continue;  // skip corrupted or empty files
                }

                // cleanup old files if exists
                if ($existingItem && !empty($existingItem->$field)) {
                    $oldFiles = is_array($existingItem->$field)
                        ? $existingItem->$field
                        : json_decode($existingItem->$field, true);

                    if (is_array($oldFiles)) {
                        foreach ($oldFiles as $old) {
                            if (!empty($old) && Storage::disk('public')->exists($old)) {
                                Storage::disk('public')->delete($old);
                            }
                        }
                    } elseif (!empty($oldFiles) && Storage::disk('public')->exists($oldFiles)) {
                        Storage::disk('public')->delete($oldFiles);
                    }
                }

                $extension = strtolower($file->getClientOriginalExtension());

                // check if it's an image
                $folder = in_array($extension, ['jpg', 'jpeg', 'png', 'gif', 'webp'])
                    ? "$modelName/images"
                    : "$modelName/files";

                $storedPaths[] = $file->store($folder, 'public');
            }

            if (!empty($storedPaths)) {
                // if multiple files -> json, else single string
                $uploads[$field] = count($storedPaths) > 1
                    ? $storedPaths
                    : $storedPaths[0];
            }
        }

        return $uploads;
    }

    public function createOperationCounter(Request $request)
    {
        $docType = $request->input('docType');

        DB::transaction(function () use ($docType) {
            // lock bach manb9awsh race condition
            $counter = DB::table('operation_counters')
                ->where('docType', $docType)
                ->lockForUpdate()
                ->first();

            if (!$counter) {
                // ila makaynch docType, ndiruha b 1
                DB::table('operation_counters')->insert([
                    'docType' => $docType,
                    'last_number' => 1,
                    'created_at' => now(),
                    'updated_at' => now(),
                ]);

                $nextNumber = 1;  // initial number
            } else {
                // ila kayna, nzed 1 w nupdate
                $nextNumber = $counter->last_number + 1;
                DB::table('operation_counters')
                    ->where('docType', $docType)
                    ->update(['last_number' => $nextNumber, 'updated_at' => now()]);
            }

            return $nextNumber;
        });
    }

    protected $docTypeCodes = [
        //sales
        'invoicesale'       => 'FA',
        'returnsale'        => 'AVV',
        'estimate'          => 'DEV',
        'deliverynote'      => 'BL',
        'possale'           => 'POS',
        //purchases
        'pricerequest'      => 'DP',
        'purchaseorder'     => 'BC',
        'receiptnote'       => 'BR',
        'invoicepurchase'   => 'FA',
        'returnpurchase'    => 'AVA',
        'expense'           => 'DEP',
        //stock movements
        'exitnote'          => 'BS',
        'entrynote'         => 'BE',
        'transfernote'      => 'BT',
        'adjustmentnote'    => 'BAJ',
        //production
        'orderprod'         => 'OP',
        'generateprod'      => 'BP',

    ];
}
