<?php

namespace App\Http\Controllers\Reports;

use App\Enums\ItemTransactionUniqueCode;
use App\Http\Controllers\Controller;
use App\Models\Items\Item;
use App\Models\Items\ItemBatchQuantity;
use App\Models\Items\ItemBatchTransaction;
use App\Models\Items\ItemSerialTransaction;
use App\Models\Items\ItemTransaction;
use App\Models\User;
use App\Services\ItemTransactionService;
use App\Services\StockImpact;
use App\Traits\FormatNumber;
use App\Traits\FormatsDateInputs;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class ItemTransactionReportController extends Controller
{
    use FormatNumber;
    use FormatsDateInputs;

    private $stockImpact;

    private $itemTransactionService;

    public function __construct(StockImpact $stockImpact, ItemTransactionService $itemTransactionService)
    {
        $this->stockImpact = $stockImpact;
        $this->itemTransactionService = $itemTransactionService;
    }

    /**
     * Report -> Item Transaction -> Serial
     * */
    public function getBatchWiseTransactionRecords(Request $request): JsonResponse
    {
        try {
            // Validation rules
            $rules = [
                'from_date' => ['required', 'date_format:'.implode(',', $this->getDateFormats())],
                'to_date' => ['required', 'date_format:'.implode(',', $this->getDateFormats())],
            ];

            $validator = Validator::make($request->all(), $rules);

            if ($validator->fails()) {
                throw new \Exception($validator->errors()->first());
            }

            $fromDate = $request->input('from_date');
            $fromDate = $this->toSystemDateFormat($fromDate);
            $toDate = $request->input('to_date');
            $toDate = $this->toSystemDateFormat($toDate);
            $itemId = $request->input('item_id');
            $brandId = $request->input('brand_id');
            $batchMasterId = $request->input('batch_id');
            $warehouseId = $request->input('warehouse_id');

            // If warehouseId is not provided, fetch warehouses accessible to the user
            $warehouseIds = $warehouseId ? [$warehouseId] : User::find(auth()->id())->getAccessibleWarehouses()->pluck('id');

            $preparedData = ItemBatchTransaction::with('itemTransaction.item.brand', 'itemBatchMaster')
                ->when($batchMasterId, function ($query) use ($batchMasterId) {
                    $query->where('item_batch_master_id', $batchMasterId);
                })
                ->whereIn('warehouse_id', $warehouseIds)
                ->when($itemId, function ($query) use ($itemId) {
                    return $query->whereHas('itemTransaction', function ($query) use ($itemId) {
                        $query->where('item_id', $itemId);
                    });
                })
                ->when($brandId, function ($query) use ($brandId) {
                    return $query->whereHas('itemTransaction.item', function ($query) use ($brandId) {
                        $query->where('brand_id', $brandId); // Corrected to `brand_id`
                    });
                })
                ->when($fromDate, function ($query) use ($fromDate, $toDate) {
                    return $query->whereHas('itemTransaction', function ($query) use ($fromDate, $toDate) {
                        $query->whereBetween('transaction_date', [$fromDate, $toDate]);
                    });
                })
                ->get();

            if ($preparedData->count() == 0) {
                throw new \Exception('No Records Found!!');
            }

            $recordsArray = [];

            foreach ($preparedData as $data) {
                $transactionType = $data->itemTransaction->transaction_type;
                $uniqueCode = $data->itemTransaction->unique_code;

                /**
                 * Make changes in transaction type based on unique code
                 * */
                $suffix = match ($uniqueCode) {
                    ItemTransactionUniqueCode::STOCK_TRANSFER->value => '(OUT)',
                    ItemTransactionUniqueCode::STOCK_RECEIVE->value => '(IN)',
                    default => '',
                };

                $recordsArray[] = [
                    'transaction_date' => $this->toUserDateFormat($data->itemTransaction->transaction_date),
                    'transaction_type' => $transactionType.$suffix,
                    'invoice_or_bill_code' => $data->itemTransaction->transaction->getTableCode() ?? '',
                    'party_name' => $data->itemTransaction->transaction->party ? $data->itemTransaction->transaction->party->getFullName() : '',
                    'warehouse' => $data->warehouse->name,
                    'item_name' => $data->itemTransaction->item->name,
                    'brand_name' => $data->itemTransaction->item->brand->name ?? '',
                    'batch_no' => $data->itemBatchMaster->batch_no ?? '',
                    'mfg_date' => $data->itemBatchMaster->formatted_mfg_date ?? '',
                    'exp_date' => $data->itemBatchMaster->formatted_exp_date ?? '',
                    'model_no' => $data->itemBatchMaster->model_no ?? '',
                    'color' => $data->itemBatchMaster->color ?? '',
                    'size' => $data->itemBatchMaster->size ?? '',
                    'quantity' => $data->quantity,
                    'stock_impact' => $this->stockImpact->returnStockImpact($data->itemTransaction->unique_code, $data->quantity)['quantity'],
                    'stock_impact_color' => $this->stockImpact->returnStockImpact($data->itemTransaction->unique_code, $data->quantity)['color'],
                ];
            }

            return response()->json([
                'status' => true,
                'message' => 'Records are retrieved!!',
                'data' => $recordsArray,
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'status' => false,
                'message' => $e->getMessage(),
            ], 409);

        }
    }

    /**
     * Report -> Item Transaction -> Serial
     * */
    public function getSerialWiseTransactionRecords(Request $request): JsonResponse
    {
        try {
            // Validation rules
            $rules = [
                'from_date' => ['required', 'date_format:'.implode(',', $this->getDateFormats())],
                'to_date' => ['required', 'date_format:'.implode(',', $this->getDateFormats())],
            ];

            $validator = Validator::make($request->all(), $rules);

            if ($validator->fails()) {
                throw new \Exception($validator->errors()->first());
            }

            $fromDate = $request->input('from_date');
            $fromDate = $this->toSystemDateFormat($fromDate);
            $toDate = $request->input('to_date');
            $toDate = $this->toSystemDateFormat($toDate);
            $itemId = $request->input('item_id');
            $brandId = $request->input('brand_id');
            $serialMasterId = $request->input('serial_id');
            $warehouseId = $request->input('warehouse_id');

            // If warehouseId is not provided, fetch warehouses accessible to the user
            $warehouseIds = $warehouseId ? [$warehouseId] : User::find(auth()->id())->getAccessibleWarehouses()->pluck('id');

            $preparedData = ItemSerialTransaction::with('itemTransaction.item.brand', 'itemSerialMaster')
                ->when($serialMasterId, function ($query) use ($serialMasterId) {
                    $query->where('item_serial_master_id', $serialMasterId); // Corrected to 'id'
                })
                ->whereIn('warehouse_id', $warehouseIds)
                ->when($itemId, function ($query) use ($itemId) {
                    return $query->whereHas('itemTransaction', function ($query) use ($itemId) {
                        $query->where('item_id', $itemId);
                    });
                })
                ->when($brandId, function ($query) use ($brandId) {
                    return $query->whereHas('itemTransaction.item', function ($query) use ($brandId) {
                        $query->where('brand_id', $brandId); // Corrected to `brand_id`
                    });
                })
                ->when($fromDate, function ($query) use ($fromDate, $toDate) {
                    return $query->whereHas('itemTransaction', function ($query) use ($fromDate, $toDate) {
                        $query->whereBetween('transaction_date', [$fromDate, $toDate]);
                    });
                })
                ->get();

            if ($preparedData->count() == 0) {
                throw new \Exception('No Records Found!!');
            }

            $recordsArray = [];

            foreach ($preparedData as $data) {
                $transactionType = $data->itemTransaction->transaction_type;
                $uniqueCode = $data->unique_code;

                /**
                 * Make changes in transaction type based on unique code
                 * */
                $suffix = match ($uniqueCode) {
                    ItemTransactionUniqueCode::STOCK_TRANSFER->value => '(OUT)',
                    ItemTransactionUniqueCode::STOCK_RECEIVE->value => '(IN)',
                    default => '',
                };

                $recordsArray[] = [
                    'transaction_date' => $this->toUserDateFormat($data->itemTransaction->transaction_date),
                    'transaction_type' => $transactionType.$suffix,
                    'invoice_or_bill_code' => $data->itemTransaction->transaction->getTableCode() ?? '',
                    'party_name' => $data->itemTransaction->transaction->party ? $data->itemTransaction->transaction->party->getFullName() : '',
                    'warehouse' => $data->warehouse->name,
                    'item_name' => $data->itemTransaction->item->name,
                    'brand_name' => $data->itemTransaction->item->brand->name ?? '',
                    'serial_code' => $data->itemSerialMaster->serial_code ?? '',
                    'stock_impact' => $this->stockImpact->returnStockImpact($data->unique_code, quantity: 1)['color'],
                ];
            }

            return response()->json([
                'status' => true,
                'message' => 'Records are retrieved!!',
                'data' => $recordsArray,
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'status' => false,
                'message' => $e->getMessage(),
            ], 409);

        }
    }

    public function getGeneralTransactionRecords(Request $request): JsonResponse
    {
        try {
            // Validation rules
            $rules = [
                'from_date' => ['required', 'date_format:'.implode(',', $this->getDateFormats())],
                'to_date' => ['required', 'date_format:'.implode(',', $this->getDateFormats())],
            ];

            $validator = Validator::make($request->all(), $rules);

            if ($validator->fails()) {
                throw new \Exception($validator->errors()->first());
            }

            $fromDate = $request->input('from_date');
            $fromDate = $this->toSystemDateFormat($fromDate);
            $toDate = $request->input('to_date');
            $toDate = $this->toSystemDateFormat($toDate);
            $itemId = $request->input('item_id');
            $brandId = $request->input('brand_id');
            $warehouseId = $request->input('warehouse_id');

            // If warehouseId is not provided, fetch warehouses accessible to the user
            $warehouseIds = $warehouseId ? [$warehouseId] : User::find(auth()->id())->getAccessibleWarehouses()->pluck('id');

            $preparedData = ItemTransaction::with('item.brand')
                ->when($itemId, function ($query) use ($itemId) {
                    return $query->where('item_id', $itemId);
                })
                ->whereIn('warehouse_id', $warehouseIds)
                ->when($brandId, function ($query) use ($brandId) {
                    return $query->whereHas('item', function ($query) use ($brandId) {
                        $query->where('brand_id', $brandId); // Corrected to `brand_id`
                    });
                })
                ->when($fromDate, function ($query) use ($fromDate, $toDate) {
                    return $query->whereBetween('transaction_date', [$fromDate, $toDate]);
                })
                ->get();

            if ($preparedData->count() == 0) {
                throw new \Exception('No Records Found!!');
            }

            $recordsArray = [];

            foreach ($preparedData as $data) {
                $transactionType = $data->transaction_type;
                $uniqueCode = $data->unique_code;

                /**
                 * Make changes in transaction type based on unique code
                 * */
                $suffix = match ($uniqueCode) {
                    ItemTransactionUniqueCode::STOCK_TRANSFER->value => '(OUT)',
                    ItemTransactionUniqueCode::STOCK_RECEIVE->value => '(IN)',
                    default => '',
                };

                $recordsArray[] = [
                    'transaction_date' => $this->toUserDateFormat($data->transaction_date),
                    'transaction_type' => $transactionType.$suffix,
                    'invoice_or_bill_code' => $data->transaction->getTableCode() ?? '',
                    'party_name' => $data->transaction->party ? $data->transaction->party->getFullName() : '',
                    'warehouse' => $data->warehouse->name,
                    'item_name' => $data->item->name,
                    'brand_name' => $data->item->brand->name ?? '',
                    'quantity' => $this->formatWithPrecision($data->quantity, comma: false),
                    'stock_impact' => $this->stockImpact->returnStockImpact($data->unique_code, $data->quantity)['quantity'],
                ];
            }

            return response()->json([
                'status' => true,
                'message' => 'Records are retrieved!!',
                'data' => $recordsArray,
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'status' => false,
                'message' => $e->getMessage(),
            ], 409);

        }
    }

    public function getExpiredItemRecords(Request $request): JsonResponse
    {
        try {
            // Validation rules
            $rules = [
                'from_date' => ['required', 'date_format:'.implode(',', $this->getDateFormats())],
                'to_date' => ['required', 'date_format:'.implode(',', $this->getDateFormats())],
            ];

            $validator = Validator::make($request->all(), $rules);

            if ($validator->fails()) {
                throw new \Exception($validator->errors()->first());
            }

            /**
             * Radio Button Values:
             * use_date
             * expired_till_date
             * days_remainig_to_expire
             * */
            $filterType = $request->filter_type;

            /**
             * Days Remaining
             * */
            $daysRemaining = $request->days_remaining;

            $fromDate = $request->input('from_date');
            $fromDate = $this->toSystemDateFormat($fromDate);

            $toDate = $request->input('to_date');
            $toDate = $this->toSystemDateFormat($toDate);

            $itemId = $request->input('item_id');
            $brandId = $request->input('brand_id');
            $batchMasterId = $request->input('batch_id');
            $warehouseId = $request->input('warehouse_id');

            // If warehouseId is not provided, fetch warehouses accessible to the user
            $warehouseIds = $warehouseId ? [$warehouseId] : User::find(auth()->id())->getAccessibleWarehouses()->pluck('id');

            $preparedData = ItemBatchQuantity::with('itemBatchMaster.item')
                ->when($batchMasterId, function ($query) use ($batchMasterId) {
                    $query->where('item_batch_master_id', $batchMasterId);
                })
                ->whereIn('warehouse_id', $warehouseIds)
                ->when($itemId, function ($query) use ($itemId) {
                    $query->where('item_id', $itemId);
                })
                ->when($brandId, function ($query) use ($brandId) {
                    return $query->whereHas('itemBatchMaster.item', function ($query) use ($brandId) {
                        $query->where('brand_id', $brandId); // Corrected to `brand_id`
                    });
                })
                ->when($fromDate, function ($query) use ($fromDate, $toDate, $filterType, $daysRemaining) {
                    return $query->whereHas('itemBatchMaster', function ($query) use ($fromDate, $toDate, $filterType, $daysRemaining) {
                        if ($filterType == 'use_date') {
                            $query->whereBetween('exp_date', [$fromDate, $toDate]);
                        } elseif ($filterType == 'expired_till_date') {
                            $query->where('exp_date', '<=', $toDate);
                        } else {
                            // days_remainig_to_expire
                            $today = Carbon::today();
                            $futureDate = $today->copy()->addDays($daysRemaining);
                            $query->whereBetween('exp_date', [$today, $futureDate]);
                        }
                    });
                })
                ->get();

            if ($preparedData->count() == 0) {
                throw new \Exception('No Records Found!!');
            }

            $recordsArray = [];

            foreach ($preparedData as $data) {
                $recordsArray[] = [
                    'warehouse' => $data->warehouse->name,
                    'item_name' => $data->itemBatchMaster->item->name,
                    'brand_name' => $data->itemBatchMaster->item->brand->name ?? '',
                    'batch_no' => $data->itemBatchMaster->batch_no ?? '',
                    'mfg_date' => $data->itemBatchMaster->formatted_mfg_date ?? '',
                    'exp_date' => $data->itemBatchMaster->formatted_exp_date ?? '',
                    'days_until_expiry' => $this->itemTransactionService->daysDifferenceByDate($data->itemBatchMaster->exp_date),
                    'model_no' => $data->itemBatchMaster->model_no ?? '',
                    'color' => $data->itemBatchMaster->color ?? '',
                    'size' => $data->itemBatchMaster->size ?? '',
                    'quantity' => $data->quantity,
                ];
            }

            return response()->json([
                'status' => true,
                'message' => 'Records are retrieved!!',
                'data' => $recordsArray,
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'status' => false,
                'message' => $e->getMessage(),
            ], 409);

        }
    }

    public function getReorderItemRecords(Request $request): JsonResponse
    {
        try {
            $itemId = $request->input('item_id');
            $brandId = $request->input('brand_id');
            $categoryId = $request->input('item_category_id');

            $preparedData = Item::with('baseUnit')->when($itemId, function ($query) use ($itemId) {
                return $query->where('id', $itemId);
            })
                ->when($categoryId, function ($query) use ($categoryId) {
                    return $query->where('item_category_id', $categoryId);
                })
                ->when($brandId, function ($query) use ($brandId) {
                    $query->where('brand_id', $brandId); // Corrected to `brand_id`
                })
                ->where('current_stock', '<=', 'min_stock')
                ->get();

            if ($preparedData->count() == 0) {
                throw new \Exception('No Records Found!!');
            }

            $recordsArray = [];

            foreach ($preparedData as $data) {
                $recordsArray[] = [
                    'item_name' => $data->name,
                    'brand_name' => $data->brand->name ?? '',
                    'category_name' => $data->category->name,
                    'min_stock' => $data->min_stock,
                    'quantity' => $this->formatWithPrecision($data->current_stock, comma: false),
                    'unit_name' => $data->baseUnit->name,
                ];
            }

            return response()->json([
                'status' => true,
                'message' => 'Records are retrieved!!',
                'data' => $recordsArray,
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'status' => false,
                'message' => $e->getMessage(),
            ], 409);

        }
    }
}
