<?php

namespace App\Services;

use App\Models\Bill;
use App\Models\Bill_details;
use App\Models\Product;
use App\Models\Supplier;
use App\Models\rate;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class BillServices
{
	public function getBillData(
		int $page,
		int $length,
		?string $searchValue,
		?string $status,
		int $companyId
	): LengthAwarePaginator {
		$query = Bill::query()
			->leftJoin('supplier', 'supplier.id', '=', 'bill.supplier_id')
			->select([
				'bill.id',
				'bill.bill_identifier',
				'bill.status',
				'bill.total',
				'bill.debt',
				'bill.expiration_date',
				'bill.alert_date',
				'bill.supplier_id',
				'supplier.supplier_name as supplier_name',
				'bill.created_at',
			])
			->where('bill.company_id', $companyId)
			->orderByDesc('bill.id');

		if (! empty($searchValue)) {
			$query->where(function ($subQuery) use ($searchValue): void {
				$subQuery->where('bill.bill_identifier', 'like', "%{$searchValue}%")
					->orWhere('bill.status', 'like', "%{$searchValue}%")
					->orWhere('supplier.supplier_name', 'like', "%{$searchValue}%")
					->orWhere('bill.total', 'like', "%{$searchValue}%")
					->orWhere('bill.debt', 'like', "%{$searchValue}%");
			});
		}

		if ($status !== null && $status !== '') {
			$query->where('bill.status', $status);
		}

		$paginator = $query->paginate($length, ['*'], 'page', $page);

		$paginator->getCollection()->transform(function ($bill) {
			return [
				'id' => $bill->id,
				'bill_identifier' => $bill->bill_identifier,
				'status' => $bill->status,
				'status_label' => ucfirst((string) $bill->status),
				'supplier_name' => $bill->supplier_name ?: 'Sin proveedor',
				'total' => (float) ($bill->total ?? 0),
				'debt' => (float) ($bill->debt ?? 0),
				'total_formatted' => number_format((float) ($bill->total ?? 0), 2, ',', '.'),
				'debt_formatted' => number_format((float) ($bill->debt ?? 0), 2, ',', '.'),
				'expiration_date' => $bill->expiration_date ? date('d/m/Y', strtotime((string) $bill->expiration_date)) : 'N/A',
				'alert_date' => $bill->alert_date ? date('d/m/Y', strtotime((string) $bill->alert_date)) : 'N/A',
				'created_at_formatted' => $bill->created_at ? date('d/m/Y H:i', strtotime((string) $bill->created_at)) : 'N/A',
			];
		});

		return $paginator;
	}

	public function createBill(Request $request, int $userId, int $companyId): Bill
	{
		$validated = $this->validateBillRequest($request, true);
		$details = $this->extractAndValidateBillDetails($request);
		$products = $this->loadProductsForDetails($details, $companyId);

		$createdAt = Carbon::now();
		$expirationDate = $createdAt->copy()->addMonth()->startOfDay();
		$alertDate = $expirationDate->copy()->subDays(73)->startOfDay();

		$validated['bill_identifier'] = $this->nextBillIdentifier($companyId);
		$validated['expiration_date'] = $expirationDate->toDateString();
		$validated['alert_date'] = $alertDate->toDateString();

		$rateValue = $this->resolveRateValue($validated);
		$totals = $this->calculateTotals($details, $products, $rateValue);

		$validated['user_id'] = $userId;
		$validated['company_id'] = $companyId;
		$validated['created_at'] = $createdAt;
		$validated['updated_at'] = $createdAt;
		$validated['amount_rate'] = $rateValue;
		$validated['total'] = $totals['total'];
		$validated['aux_total'] = $totals['aux_total'];

		$status = (string) ($validated['status'] ?? 'pendiente');
		if ($status === 'pendiente') {
			$validated['debt'] = $totals['total'];
			$validated['aux_debt'] = $totals['aux_total'];
		} else {
			$validated['debt'] = 0;
			$validated['aux_debt'] = 0;
		}

		return DB::transaction(function () use ($validated, $details, $products, $rateValue) {
			$bill = Bill::create($validated);

			foreach ($details as $detail) {
				$productId = (int) $detail['product_id'];
				$quantity = (int) $detail['quantity'];
				$product = $products->get($productId);
				$baseUnitPrice = round((float) ($product?->purchase_price ?? 0), 2);
				$unitPrice = round($baseUnitPrice * $rateValue, 2);
				$subtotal = round($unitPrice * $quantity, 2);

				Bill_details::create([
					'bill_id' => (int) $bill->id,
					'product_id' => $productId,
					'quantity' => $quantity,
					'unit_price' => $unitPrice,
					'subtotal' => $subtotal,
				]);
			}

			return $bill;
		});
	}

	public function updateBill(Request $request, Bill $bill): Bill
	{
		$validated = $this->validateBillRequest($request, false, $bill);
		$bill->update($validated);

		return $bill->refresh();
	}

	public function deleteBill(Bill $bill): void
	{
		$bill->delete();
	}

	public function searchSuppliers(?string $term): Collection
	{
		$query = Supplier::query()
			->select(['id', 'supplier_name'])
			->where('is_active', 1)
			->orderBy('supplier_name')
			->limit(15);

		if (! empty($term)) {
			$query->where('supplier_name', 'like', "%{$term}%");
		}

		return $query->get()->map(function (Supplier $supplier) {
			return [
				'id' => $supplier->id,
				'supplier_name' => $supplier->supplier_name,
			];
		});
	}

	public function getActiveRateValue(): float
	{
		$activeRate = rate::query()
			->where('is_active', 1)
			->latest('id')
			->first();

		$value = (float) ($activeRate?->usd_rate ?? 1);

		return $value > 0 ? $value : 1.0;
	}

	public function searchProducts(?string $term, ?int $companyId = null): Collection
	{
		$query = Product::query()
			->select(['id', 'product_name', 'purchase_price', 'current_stock'])
			->where('is_active', 1)
			->orderBy('product_name')
			->limit(20);

		if ($companyId !== null && $companyId > 0) {
			$query->where('company_id', $companyId);
		}

		if (! empty($term)) {
			$query->where(function ($subQuery) use ($term): void {
				$subQuery->where('product_name', 'like', "%{$term}%")
					->orWhere('barcode', 'like', "%{$term}%");
			});
		}

		return $query->get()->map(function (Product $product) {
			return [
				'id' => $product->id,
				'product_name' => $product->product_name,
				'purchase_price' => round((float) ($product->purchase_price ?? 0), 2),
				'current_stock' => (int) ($product->current_stock ?? 0),
			];
		});
	}

	public function quickCreateProduct(array $payload, ?int $companyId = null): Product
	{
		$normalizedName = trim((string) ($payload['product_name'] ?? ''));

		$existing = Product::query()
			->whereRaw('LOWER(product_name) = ?', [mb_strtolower($normalizedName)])
			->first();

		if ($existing) {
			return $existing;
		}

		$purchasePrice = round((float) ($payload['purchase_price'] ?? 0), 2);
		$salePrice = round((float) ($payload['sale_price'] ?? 0), 2);

		$data = [
			'product_name' => $normalizedName,
			'description' => $payload['description'] ?? null,
			'sale_price' => $salePrice,
			'purchase_price' => $purchasePrice,
			'current_stock' => 0,
			'minimum_stock' => 0,
			'barcode' => null,
			'supplier_id' => isset($payload['supplier_id']) && $payload['supplier_id'] !== '' ? (int) $payload['supplier_id'] : null,
			'category_id' => isset($payload['category_id']) && $payload['category_id'] !== '' ? (int) $payload['category_id'] : null,
			'is_active' => 1,
		];

		if ($companyId !== null && $companyId > 0) {
			$data['company_id'] = $companyId;
		}

		return Product::create($data);
	}

	public function quickCreateSupplier(string $supplierName): Supplier
	{
		$normalizedName = trim($supplierName);

		$existing = Supplier::query()
			->where('supplier_name', $normalizedName)
			->first();

		if ($existing) {
			return $existing;
		}

		return Supplier::create([
			'supplier_name' => $normalizedName,
			'contact_name' => $normalizedName,
			'is_active' => 1,
		]);
	}

	private function nextBillIdentifier(int $companyId): int
	{
		$lastIdentifier = (int) Bill::query()
			->where('company_id', $companyId)
			->max('bill_identifier');

		return max(1, $lastIdentifier + 1);
	}

	private function validateBillRequest(Request $request, bool $requireDetails = false, ?Bill $bill = null): array
	{
		$rules = [
			'bill_identifier' => ['nullable', 'integer', 'min:1'],
			'status' => ['required', 'string', 'in:completada,pendiente,anulada'],
			'total' => ['nullable', 'numeric', 'min:0'],
			'debt' => ['nullable', 'numeric', 'min:0'],
			'aux_total' => ['nullable', 'numeric', 'min:0'],
			'aux_debt' => ['nullable', 'numeric', 'min:0'],
			'amount_rate' => ['nullable', 'numeric', 'min:0'],
			'expiration_date' => ['nullable', 'date'],
			'alert_date' => ['nullable', 'date'],
			'supplier_id' => ['nullable', 'integer', 'exists:supplier,id'],
		];

		if ($requireDetails) {
			$rules['bill_details_json'] = ['required', 'string'];
		}

		return $request->validate($rules, [
			'bill_identifier.integer' => 'El identificador de factura debe ser numérico.',
			'bill_identifier.min' => 'El identificador de factura debe ser mayor a 0.',
			'status.required' => 'El estado es obligatorio.',
			'status.in' => 'El estado seleccionado no es válido.',
			'total.numeric' => 'El total debe ser un número válido.',
			'debt.numeric' => 'La deuda debe ser un número válido.',
			'aux_total.numeric' => 'El total auxiliar debe ser un número válido.',
			'aux_debt.numeric' => 'La deuda auxiliar debe ser un número válido.',
			'amount_rate.numeric' => 'El monto de tasa debe ser un número válido.',
			'expiration_date.date' => 'La fecha de vencimiento no es válida.',
			'alert_date.date' => 'La fecha de alerta no es válida.',
			'supplier_id.exists' => 'El proveedor seleccionado no existe.',
			'bill_details_json.required' => 'Debes agregar al menos un producto en el detalle de factura.',
		], [
			'bill_identifier' => 'identificador de factura',
			'status' => 'estado',
			'total' => 'total',
			'debt' => 'deuda',
			'aux_total' => 'total auxiliar',
			'aux_debt' => 'deuda auxiliar',
			'amount_rate' => 'monto de tasa',
			'expiration_date' => 'fecha de vencimiento',
			'alert_date' => 'fecha de alerta',
			'supplier_id' => 'proveedor',
			'bill_details_json' => 'detalle de factura',
		]);
	}

	private function extractAndValidateBillDetails(Request $request): array
	{
		$raw = $request->input('bill_details_json');
		$details = json_decode((string) $raw, true);

		if (! is_array($details) || empty($details)) {
			throw ValidationException::withMessages([
				'bill_details_json' => 'Debes agregar al menos un producto en el detalle de factura.',
			]);
		}

		$normalized = [];

		foreach ($details as $index => $item) {
			$productId = (int) ($item['product_id'] ?? 0);
			$quantity = (int) ($item['quantity'] ?? 0);

			if ($productId <= 0 || $quantity <= 0) {
				$line = $index + 1;
				throw ValidationException::withMessages([
					'bill_details_json' => "El item {$line} del detalle no es válido.",
				]);
			}

			$normalized[] = [
				'product_id' => $productId,
				'quantity' => $quantity,
			];
		}

		return $normalized;
	}

	private function loadProductsForDetails(array $details, ?int $companyId = null): Collection
	{
		$productIds = collect($details)->pluck('product_id')->unique()->values();

		$query = Product::query()
			->whereIn('id', $productIds)
			->where('is_active', 1)
			->lockForUpdate();

		if ($companyId !== null && $companyId > 0) {
			$query->where('company_id', $companyId);
		}

		$products = $query->get()->keyBy('id');

		if ($products->count() !== $productIds->count()) {
			throw ValidationException::withMessages([
				'bill_details_json' => 'Algunos productos no existen o están inactivos.',
			]);
		}

		return $products;
	}

	private function resolveRateValue(array $validated): float
	{
		$inputRate = isset($validated['amount_rate']) ? (float) $validated['amount_rate'] : 0;
		if ($inputRate > 0) {
			return round($inputRate, 4);
		}

		return round($this->getActiveRateValue(), 4);
	}

	private function calculateTotals(array $details, Collection $products, float $rateValue): array
	{
		$auxTotal = 0.0;
		$total = 0.0;

		foreach ($details as $detail) {
			$product = $products->get((int) $detail['product_id']);
			$quantity = (int) $detail['quantity'];
			$baseUnitPrice = round((float) ($product?->purchase_price ?? 0), 2);
			$lineAuxSubtotal = round($baseUnitPrice * $quantity, 2);
			$lineSubtotal = round($lineAuxSubtotal * $rateValue, 2);

			$auxTotal += $lineAuxSubtotal;
			$total += $lineSubtotal;
		}

		return [
			'aux_total' => round($auxTotal, 2),
			'total' => round($total, 2),
		];
	}

	public function getSupplierData(int $page, int $length, ?string $searchValue): LengthAwarePaginator
	{
		$query = Supplier::query()
			->select(['id', 'supplier_name', 'rif'])
			->where('is_active', 1)
			->orderBy('supplier_name');

		if (!empty($searchValue)) {
			$query->where(function ($subQuery) use ($searchValue) {
				$subQuery->where('supplier_name', 'like', "%{$searchValue}%")
					->orWhere('rif', 'like', "%{$searchValue}%");
			});
		}

		return $query->paginate($length, ['*'], 'page', $page);
	}

	public function getProductData(int $page, int $length, ?string $searchValue, ?int $companyId = null): LengthAwarePaginator
	{
		$query = Product::query()
			->select(['id', 'product_name', 'purchase_price', 'sale_price', 'current_stock', 'barcode'])
			->where('is_active', 1)
			->orderBy('product_name');

		if ($companyId !== null && $companyId > 0) {
			$query->where('company_id', $companyId);
		}

		if (!empty($searchValue)) {
			$query->where(function ($subQuery) use ($searchValue) {
				$subQuery->where('product_name', 'like', "%{$searchValue}%")
					->orWhere('barcode', 'like', "%{$searchValue}%");
			});
		}

		return $query->paginate($length, ['*'], 'page', $page);
	}
}
