<?php

namespace App\Modules\POS\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Responses\ApiResponse;
use App\Modules\Accounting\Models\AccountTransaction;
use App\Modules\Accounting\Models\FinancialAccount;
use App\Modules\Inventory\Models\Product;
use App\Modules\POS\Models\Sale;
use App\Modules\POS\Requests\StoreSaleRequest;
use App\Modules\POS\Requests\UpdateSaleRequest;
use App\Modules\POS\Resources\SaleResource;
use App\Modules\Tenant\Models\Company;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;

class SaleController extends Controller
{
    public function index(Request $request): JsonResponse
    {
        $company = $this->currentCompany($request);
        $query = $this->filteredSalesQuery($request, $company);

        if ($request->hasAny(['page', 'per_page'])) {
            $perPage = min(max((int) $request->query('per_page', 15), 1), 100);
            $paginator = $query->paginate($perPage);

            return ApiResponse::success([
                'data' => SaleResource::collection($paginator->items()),
                'meta' => [
                    'current_page' => $paginator->currentPage(),
                    'from' => $paginator->firstItem(),
                    'last_page' => $paginator->lastPage(),
                    'per_page' => $paginator->perPage(),
                    'to' => $paginator->lastItem(),
                    'total' => $paginator->total(),
                ],
            ]);
        }

        return ApiResponse::success(SaleResource::collection($query->get()));
    }

    /**
     * @return Builder<Sale>
     */
    private function filteredSalesQuery(Request $request, Company $company): Builder
    {
        return Sale::query()
            ->where('company_id', $company->id)
            ->with(['items', 'financialAccount'])
            ->when($request->query('search'), function ($query, mixed $search): void {
                $term = '%'.(string) $search.'%';
                $query->where(function ($innerQuery) use ($term): void {
                    $innerQuery
                        ->where('sale_number', 'like', $term)
                        ->orWhere('payment_method', 'like', $term)
                        ->orWhere('payment_status', 'like', $term);
                });
            })
            ->when($request->query('payment_method'), fn ($query, mixed $method) => $query->where('payment_method', (string) $method))
            ->when($request->query('payment_status'), fn ($query, mixed $status) => $query->where('payment_status', (string) $status))
            ->when($request->query('status'), fn ($query, mixed $status) => $query->where('status', (string) $status))
            ->when($request->query('date_from'), fn ($query, mixed $date) => $query->whereDate('sale_date', '>=', (string) $date))
            ->when($request->query('date_to'), fn ($query, mixed $date) => $query->whereDate('sale_date', '<=', (string) $date))
            ->latest();
    }

    public function store(StoreSaleRequest $request): JsonResponse
    {
        $company = $this->currentCompany($request);
        $data = $request->validated();

        foreach ($data['items'] as $item) {
            if (! Product::query()->where('company_id', $company->id)->whereKey($item['product_id'])->exists()) {
                return ApiResponse::error('Product not found', 404);
            }
        }

        if (! empty($data['financial_account_id']) && ! $this->accountBelongsToCompany($company, (int) $data['financial_account_id'])) {
            return ApiResponse::error('Financial account not found', 404);
        }

        $sale = DB::transaction(function () use ($company, $request, $data): Sale|JsonResponse {
            $preparedItems = [];
            $subtotal = 0.0;
            $discountTotal = 0.0;
            $taxTotal = 0.0;
            $defaultTaxRate = $this->settingFloat($company, 'default_tax_rate', 0);

            foreach ($data['items'] as $item) {
                $product = Product::query()
                    ->where('company_id', $company->id)
                    ->lockForUpdate()
                    ->findOrFail($item['product_id']);
                $quantity = (float) $item['quantity'];
                $stockBefore = (float) $product->current_stock;

                if ($stockBefore < $quantity) {
                    return ApiResponse::error("Insufficient stock for {$product->name}", 422);
                }

                $unitPrice = array_key_exists('unit_price', $item) && $item['unit_price'] !== null
                    ? (float) $item['unit_price']
                    : (float) $product->selling_price;
                $discountAmount = (float) ($item['discount_amount'] ?? 0);
                $taxRate = array_key_exists('tax_rate', $item) && $item['tax_rate'] !== null
                    ? (float) $item['tax_rate']
                    : $defaultTaxRate;
                $lineSubtotal = round($quantity * $unitPrice, 2);
                $taxAmount = round(max($lineSubtotal - $discountAmount, 0) * ($taxRate / 100), 2);
                $lineTotal = round(max($lineSubtotal - $discountAmount, 0) + $taxAmount, 2);

                $subtotal += $lineSubtotal;
                $discountTotal += $discountAmount;
                $taxTotal += $taxAmount;

                $preparedItems[] = [
                    'product' => $product,
                    'quantity' => $quantity,
                    'stock_before' => $stockBefore,
                    'stock_after' => $stockBefore - $quantity,
                    'description' => $product->name,
                    'sku' => $product->sku,
                    'unit_price' => $unitPrice,
                    'discount_amount' => $discountAmount,
                    'tax_rate' => $taxRate,
                    'tax_amount' => $taxAmount,
                    'line_total' => $lineTotal,
                ];
            }

            $total = round($subtotal - $discountTotal + $taxTotal, 2);
            $paidTotal = array_key_exists('paid_total', $data) && $data['paid_total'] !== null
                ? (float) $data['paid_total']
                : $total;
            $changeTotal = max(round($paidTotal - $total, 2), 0);

            $sale = Sale::query()->create([
                'company_id' => $company->id,
                'user_id' => $request->user()?->id,
                'financial_account_id' => $data['financial_account_id'] ?? null,
                'sale_number' => $this->nextSaleNumber($company),
                'sale_date' => $data['sale_date'] ?? now()->toDateString(),
                'subtotal' => round($subtotal, 2),
                'discount_total' => round($discountTotal, 2),
                'tax_total' => round($taxTotal, 2),
                'total' => $total,
                'paid_total' => $paidTotal,
                'change_total' => $changeTotal,
                'payment_method' => $data['payment_method'] ?? 'cash',
                'payment_status' => $paidTotal >= $total ? 'paid' : 'partial',
                'status' => 'completed',
                'notes' => $data['notes'] ?? null,
            ]);

            foreach ($preparedItems as $item) {
                $product = $item['product'];

                $sale->items()->create([
                    'product_id' => $product->id,
                    'description' => $item['description'],
                    'sku' => $item['sku'],
                    'quantity' => $item['quantity'],
                    'unit_price' => $item['unit_price'],
                    'discount_amount' => $item['discount_amount'],
                    'tax_rate' => $item['tax_rate'],
                    'tax_amount' => $item['tax_amount'],
                    'line_total' => $item['line_total'],
                ]);

                $product->stockMovements()->create([
                    'company_id' => $company->id,
                    'type' => 'sale',
                    'quantity' => -1 * $item['quantity'],
                    'stock_before' => $item['stock_before'],
                    'stock_after' => $item['stock_after'],
                    'reference_type' => Sale::class,
                    'reference_id' => $sale->id,
                    'note' => 'POS sale',
                ]);

                $product->update(['current_stock' => $item['stock_after']]);
            }

            $this->recordSaleLedger($sale);

            return $sale;
        });

        if ($sale instanceof JsonResponse) {
            return $sale;
        }

        return ApiResponse::success(
            new SaleResource($sale->load(['items', 'financialAccount'])),
            'Sale completed successfully',
            201
        );
    }

    public function show(Request $request, Sale $sale): JsonResponse
    {
        $company = $this->currentCompany($request);

        if ((int) $sale->company_id !== (int) $company->id) {
            return ApiResponse::error('Sale not found', 404);
        }

        return ApiResponse::success(new SaleResource($sale->load(['items', 'financialAccount'])));
    }

    public function update(UpdateSaleRequest $request, Sale $sale): JsonResponse
    {
        $company = $this->currentCompany($request);

        if ((int) $sale->company_id !== (int) $company->id) {
            return ApiResponse::error('Sale not found', 404);
        }

        $data = $request->validated();

        if (($data['status'] ?? null) === 'voided') {
            if ($sale->status === 'voided') {
                return ApiResponse::error('Sale is already voided', 422);
            }

            $sale = DB::transaction(function () use ($company, $sale, $data): Sale {
                $sale->load('items');

                foreach ($sale->items as $item) {
                    $product = Product::query()
                        ->where('company_id', $company->id)
                        ->lockForUpdate()
                        ->findOrFail($item->product_id);
                    $stockBefore = (float) $product->current_stock;
                    $stockAfter = $stockBefore + (float) $item->quantity;

                    $product->stockMovements()->create([
                        'company_id' => $company->id,
                        'type' => 'sale_void',
                        'quantity' => (float) $item->quantity,
                        'stock_before' => $stockBefore,
                        'stock_after' => $stockAfter,
                        'reference_type' => Sale::class,
                        'reference_id' => $sale->id,
                        'note' => 'POS sale void',
                    ]);

                    $product->update(['current_stock' => $stockAfter]);
                }

                AccountTransaction::query()
                    ->where('company_id', $company->id)
                    ->where('source_type', Sale::class)
                    ->where('source_id', $sale->id)
                    ->delete();

                $sale->update([
                    'status' => 'voided',
                    'payment_status' => 'voided',
                    'paid_total' => 0,
                    'change_total' => 0,
                    'notes' => $data['notes'] ?? $sale->notes,
                ]);

                return $sale;
            });
        } elseif (array_key_exists('notes', $data)) {
            $sale->update(['notes' => $data['notes']]);
        }

        return ApiResponse::success(
            new SaleResource($sale->fresh()->load(['items', 'financialAccount'])),
            'Sale updated successfully'
        );
    }

    public function exportHtml(Request $request, Sale $sale): Response
    {
        $company = $this->currentCompany($request);

        if ((int) $sale->company_id !== (int) $company->id) {
            return response('Sale not found', 404);
        }

        $filename = preg_replace('/[^A-Za-z0-9_-]/', '-', $sale->sale_number).'.html';

        return response()
            ->view('pos.receipt', [
                'company' => $company,
                'sale' => $sale->load('items'),
            ])
            ->header('Content-Type', 'text/html; charset=UTF-8')
            ->header('Content-Disposition', 'inline; filename="'.$filename.'"');
    }

    private function nextSaleNumber(Company $company): string
    {
        $next = Sale::query()
            ->where('company_id', $company->id)
            ->count() + 1;

        return $this->settingPrefix($company, 'pos_prefix', 'POS').'-'.now()->format('Y').'-'.str_pad((string) $next, 6, '0', STR_PAD_LEFT);
    }

    private function settingFloat(Company $company, string $key, float $default): float
    {
        return (float) ($company->settings[$key] ?? $default);
    }

    private function settingPrefix(Company $company, string $key, string $default): string
    {
        $prefix = strtoupper((string) ($company->settings[$key] ?? $default));

        return $prefix !== '' ? $prefix : $default;
    }

    private function currentCompany(Request $request): Company
    {
        return $request->attributes->get('currentCompany');
    }

    private function accountBelongsToCompany(Company $company, int $accountId): bool
    {
        return FinancialAccount::query()
            ->where('company_id', $company->id)
            ->whereKey($accountId)
            ->exists();
    }

    private function recordSaleLedger(Sale $sale): void
    {
        if (! $sale->financial_account_id || (float) $sale->paid_total <= 0) {
            return;
        }

        AccountTransaction::query()->create([
            'company_id' => $sale->company_id,
            'financial_account_id' => $sale->financial_account_id,
            'source_type' => Sale::class,
            'source_id' => $sale->id,
            'transaction_date' => $sale->sale_date,
            'type' => 'income',
            'direction' => 'inflow',
            'amount' => $sale->paid_total,
            'description' => 'POS sale '.$sale->sale_number,
            'reference' => $sale->sale_number,
            'notes' => $sale->notes,
        ]);
    }
}
