<?php

namespace App\Domains\Sale\Services;

use App\Domains\Logging\Models\ValueChange;
use App\Domains\Purchase\Models\Purchase;
use App\Domains\Purchase\Services\CheckoutService;
use App\Domains\Tenant\Models\Tenant;
use App\Domains\Ticket\Models\Ticket;
use App\Domains\Ticket\Services\TicketPresentationResolver;
use App\Domains\Ticket\Services\TicketValidityResolver;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;

class AdminAppSaleService
{
    public function __construct(
        protected CheckoutService $checkoutService,
    ) {}

    public function confirmedSalesTotal(Tenant $tenant): string
    {
        $total = Purchase::query()
            ->where('tenant_codigo', $tenant->codigo)
            ->where('status', Purchase::STATUS_PAID)
            ->sum('total');

        return number_format((float) $total, 2, '.', '');
    }

    /**
     * @param  array{
     *     q?: string|null,
     *     id?: int|null,
     *     sale_date?: string|null,
     *     status?: string|null,
     *     sort_by?: string,
     *     sort_direction?: string
     * }  $filters
     * @return LengthAwarePaginator<Purchase>
     */
    public function sales(Tenant $tenant, array $filters = []): LengthAwarePaginator
    {
        return $this->salesQuery($tenant, $filters)
            ->paginateFromRequest()
            ->withQueryString();
    }

    public function detail(Tenant $tenant, int $saleId): Purchase
    {
        return Purchase::query()
            ->where('tenant_codigo', $tenant->codigo)
            ->with('items')
            ->findOrFail($saleId);
    }

    /** @return Collection<int, Ticket> */
    public function tickets(Tenant $tenant, int $saleId): Collection
    {
        return $this->findForTenant($tenant, $saleId)
            ->tickets()
            ->with([...TicketValidityResolver::RELATIONS, ...TicketPresentationResolver::RELATIONS])
            ->orderBy('id')
            ->get();
    }

    public function confirm(Tenant $tenant, int $saleId): Purchase
    {
        $sale = $this->findForTenant($tenant, $saleId);

        return $this->saleForResponse(
            $this->checkoutService->confirmPaidPurchase($sale)
        );
    }

    public function cancel(Tenant $tenant, int $saleId): Purchase
    {
        $sale = $this->findForTenant($tenant, $saleId);

        return $this->saleForResponse(
            $this->checkoutService->cancelPurchaseFromAdmin($sale)
        );
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return Collection<int, Purchase>
     */
    public function salesForExport(Tenant $tenant, array $filters = []): Collection
    {
        return $this->salesQuery($tenant, $filters)->get();
    }

    /** @return LengthAwarePaginator<ValueChange> */
    public function modifications(Tenant $tenant): LengthAwarePaginator
    {
        return $this->modificationsQuery($tenant)
            ->paginateFromRequest()
            ->withQueryString();
    }

    /** @return Collection<int, ValueChange> */
    public function modificationsForExport(Tenant $tenant): Collection
    {
        return $this->modificationsQuery($tenant)->get();
    }

    /** @param array<string, mixed> $filters */
    protected function salesQuery(Tenant $tenant, array $filters): Builder
    {
        $sortColumns = [
            'id' => 'id',
            'date' => 'created_at',
            'customer_name' => 'nombre_apellido',
            'quantity' => 'quantity',
            'status' => 'status',
            'total' => 'total',
        ];
        $requestedSort = $filters['sort_by'] ?? 'date';
        $sortBy = array_key_exists($requestedSort, $sortColumns) ? $requestedSort : 'date';
        $requestedDirection = $filters['sort_direction'] ?? 'desc';
        $sortDirection = in_array($requestedDirection, ['asc', 'desc'], true)
            ? $requestedDirection
            : 'desc';

        return Purchase::query()
            ->where('tenant_codigo', $tenant->codigo)
            ->where('status', '!=', Purchase::STATUS_SUPERSEDED)
            ->when($filters['q'] ?? null, function (Builder $query, string $search): void {
                $term = trim($search);

                $query->where(function (Builder $query) use ($term): void {
                    $query
                        ->where('id', 'like', "%{$term}%")
                        ->orWhere('nombre_apellido', 'like', "%{$term}%")
                        ->orWhere('created_at', 'like', "%{$term}%");
                });
            })
            ->when($filters['id'] ?? null, fn (Builder $query, int $id): Builder => $query->whereKey($id))
            ->when(
                $filters['sale_date'] ?? null,
                fn (Builder $query, string $date): Builder => $query->whereDate('created_at', $date)
            )
            ->when(
                $filters['status'] ?? null,
                fn (Builder $query, string $status): Builder => $status === Purchase::STATUS_PENDING_PAYMENT
                    ? $query->whereIn('status', [
                        Purchase::STATUS_PENDING_PAYMENT,
                        Purchase::STATUS_IN_REVIEW,
                    ])
                    : $query->where('status', $status)
            )
            ->select('compras.*')
            ->selectRaw(
                '(SELECT COALESCE(SUM(purchase_items.cantidad), 0) '
                .'FROM compra_items AS purchase_items '
                .'WHERE purchase_items.compra_id = compras.id) AS quantity',
            )
            ->withCount('tickets')
            ->orderBy($sortColumns[$sortBy], $sortDirection)
            ->when($sortBy !== 'id', fn (Builder $query): Builder => $query->orderByDesc('id'));
    }

    /** @return Builder<ValueChange> */
    protected function modificationsQuery(Tenant $tenant): Builder
    {
        return ValueChange::query()
            ->where('tenant_code', $tenant->codigo)
            ->where('trackable_type', (new Purchase)->getMorphClass())
            ->with(['trackable', 'user'])
            ->orderByDesc('changed_at')
            ->orderByDesc('id');
    }

    protected function findForTenant(Tenant $tenant, int $saleId): Purchase
    {
        return Purchase::query()
            ->where('tenant_codigo', $tenant->codigo)
            ->findOrFail($saleId);
    }

    protected function saleForResponse(Purchase $sale): Purchase
    {
        /** @var Purchase */
        return Purchase::query()
            ->select('compras.*')
            ->selectRaw($this->quantityExpression())
            ->withCount('tickets')
            ->findOrFail($sale->getKey());
    }

    protected function quantityExpression(): string
    {
        return <<<'SQL'
            COALESCE(
                (SELECT SUM(compra_items.cantidad)
                 FROM compra_items
                 WHERE compra_items.compra_id = compras.id),
                0
            ) AS quantity
            SQL;
    }
}
