<?php

namespace App\Services;

use App\Enums\BookingStatus;
use App\Enums\InvoiceStatus;
use App\Enums\PaymentStatus;
use App\Enums\QuotationStatus;
use App\Enums\ReservationStatus;
use App\Enums\SpaceBlockType;
use App\Enums\SpaceOperationalStatus;
use App\Models\Booking;
use App\Models\BookingSpace;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Quotation;
use App\Models\Space;
use App\Models\SpaceBlock;
use App\Support\PropertyDate;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;

class DashboardService
{
    /**
     * @param  array<string, mixed>  $filters
     * @return array<string, mixed>
     */
    public function getDashboardData(array $filters = []): array
    {
        return [
            'reservation_cards' => $this->reservationCards($filters),
            'financial_cards' => $this->financialCards($filters),
            'space_cards' => $this->spaceCards($filters),
            'lists' => [
                'todays_events' => $this->todaysEvents($filters),
                'next_upcoming' => $this->nextUpcoming($filters),
                'expiring_quotations' => $this->expiringQuotations($filters),
                'expiring_holds' => $this->expiringHolds($filters),
                'due_invoices' => $this->dueInvoices($filters),
                'recent_payments' => $this->recentPayments($filters),
            ],
            'filters' => $filters,
            'timezone' => PropertyDate::timezone(),
        ];
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return array<string, int>
     */
    public function reservationCards(array $filters = []): array
    {
        $today = PropertyDate::now()->startOfDay();

        return [
            'upcoming_confirmed' => $this->filteredBookings($filters)
                ->hasIssuedInvoice()
                ->whereIn('status', [BookingStatus::Confirmed, BookingStatus::InProgress, BookingStatus::AwaitingDeposit])
                ->where('event_start', '>=', $today)
                ->count(),
            'today' => $this->filteredBookings($filters)
                ->hasIssuedInvoice()
                ->whereNotIn('status', [BookingStatus::Cancelled, BookingStatus::NoShow])
                ->whereDate('event_start', $today)
                ->count(),
            'past_completed' => $this->filteredBookings($filters)
                ->hasIssuedInvoice()
                ->where(function (Builder $query) use ($today): void {
                    $query->where('status', BookingStatus::Completed)
                        ->orWhere('event_end', '<', $today);
                })
                ->count(),
            'provisional' => $this->filteredBookings($filters)
                ->where('status', BookingStatus::Provisional)
                ->count(),
            'confirmed' => $this->filteredBookings($filters)
                ->where('status', BookingStatus::Confirmed)
                ->count(),
            'cancelled' => $this->filteredBookings($filters)
                ->where('status', BookingStatus::Cancelled)
                ->count(),
        ];
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return array<string, float|int>
     */
    public function financialCards(array $filters = []): array
    {
        $bookingQuery = $this->filteredBookings($filters);

        return [
            'paid' => (clone $bookingQuery)
                ->where('grand_total', '>', 0)
                ->where('outstanding_balance', '<=', 0)
                ->count(),
            'partially_paid' => (clone $bookingQuery)
                ->where('amount_paid', '>', 0)
                ->where('outstanding_balance', '>', 0)
                ->count(),
            'unpaid' => (clone $bookingQuery)
                ->where('outstanding_balance', '>', 0)
                ->where('amount_paid', '<=', 0)
                ->count(),
            'overdue_invoices' => $this->filteredInvoices($filters)
                ->whereIn('status', [InvoiceStatus::Overdue, InvoiceStatus::Issued, InvoiceStatus::Due, InvoiceStatus::PartiallyPaid])
                ->where('due_date', '<', PropertyDate::now()->startOfDay())
                ->where('outstanding_balance', '>', 0)
                ->count(),
            'total_outstanding' => (float) $this->filteredInvoices($filters)
                ->whereNotIn('status', [InvoiceStatus::Draft, InvoiceStatus::Void, InvoiceStatus::Paid, InvoiceStatus::Refunded])
                ->sum('outstanding_balance'),
            'payments_received' => (float) $this->filteredPayments($filters)
                ->where('status', PaymentStatus::Posted)
                ->sum('amount'),
        ];
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return array<string, int>
     */
    public function spaceCards(array $filters = []): array
    {
        $todayStart = PropertyDate::now()->startOfDay();
        $todayEnd = PropertyDate::now()->endOfDay();

        $activeSpaces = Space::query()
            ->when(! empty($filters['space_id']), fn (Builder $q) => $q->where('id', $filters['space_id']))
            ->where('operational_status', SpaceOperationalStatus::Active)
            ->count();

        $occupiedToday = BookingSpace::query()
            ->whereIn('reservation_status', [ReservationStatus::Confirmed, ReservationStatus::Provisional])
            ->where('setup_start', '<=', $todayEnd)
            ->where('breakdown_end', '>=', $todayStart)
            ->when(! empty($filters['space_id']), fn (Builder $q) => $q->where('space_id', $filters['space_id']))
            ->whereHas('booking', fn (Builder $q) => $this->applyBookingRelationFilters($q, $filters))
            ->distinct('space_id')
            ->count('space_id');

        $underMaintenance = Space::query()
            ->when(! empty($filters['space_id']), fn (Builder $q) => $q->where('id', $filters['space_id']))
            ->where('operational_status', SpaceOperationalStatus::UnderMaintenance)
            ->count();

        $internallyBlockedToday = SpaceBlock::query()
            ->where('block_type', SpaceBlockType::Internal)
            ->where('starts_at', '<=', $todayEnd)
            ->where('ends_at', '>=', $todayStart)
            ->when(! empty($filters['space_id']), fn (Builder $q) => $q->where('space_id', $filters['space_id']))
            ->distinct('space_id')
            ->count('space_id');

        $blockedSpaceIds = SpaceBlock::query()
            ->where('starts_at', '<=', $todayEnd)
            ->where('ends_at', '>=', $todayStart)
            ->when(! empty($filters['space_id']), fn (Builder $q) => $q->where('space_id', $filters['space_id']))
            ->pluck('space_id')
            ->unique();

        $occupiedSpaceIds = BookingSpace::query()
            ->whereIn('reservation_status', [ReservationStatus::Confirmed, ReservationStatus::Provisional])
            ->where('setup_start', '<=', $todayEnd)
            ->where('breakdown_end', '>=', $todayStart)
            ->when(! empty($filters['space_id']), fn (Builder $q) => $q->where('space_id', $filters['space_id']))
            ->whereHas('booking', fn (Builder $q) => $this->applyBookingRelationFilters($q, $filters))
            ->pluck('space_id')
            ->unique();

        $unavailableIds = $blockedSpaceIds->merge($occupiedSpaceIds)->unique();
        $maintenanceIds = Space::query()
            ->when(! empty($filters['space_id']), fn (Builder $q) => $q->where('id', $filters['space_id']))
            ->where('operational_status', SpaceOperationalStatus::UnderMaintenance)
            ->pluck('id');

        $availableToday = Space::query()
            ->when(! empty($filters['space_id']), fn (Builder $q) => $q->where('id', $filters['space_id']))
            ->where('operational_status', SpaceOperationalStatus::Active)
            ->whereNotIn('id', $unavailableIds->merge($maintenanceIds)->unique())
            ->count();

        return [
            'active_spaces' => $activeSpaces,
            'occupied_today' => $occupiedToday,
            'available_today' => $availableToday,
            'under_maintenance' => $underMaintenance,
            'internally_blocked_today' => $internallyBlockedToday,
        ];
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return Collection<int, Booking>
     */
    public function todaysEvents(array $filters = [], int $limit = 10): Collection
    {
        $today = PropertyDate::now()->startOfDay();

        return $this->filteredBookings($filters)
            ->hasIssuedInvoice()
            ->with(['customer', 'coordinator', 'eventType'])
            ->whereNotIn('status', [BookingStatus::Cancelled, BookingStatus::NoShow])
            ->whereDate('event_start', $today)
            ->orderBy('event_start')
            ->limit($limit)
            ->get();
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return Collection<int, Booking>
     */
    public function nextUpcoming(array $filters = [], int $limit = 10): Collection
    {
        $today = PropertyDate::now()->startOfDay();

        return $this->filteredBookings($filters)
            ->hasIssuedInvoice()
            ->with(['customer', 'coordinator', 'eventType'])
            ->whereDate('event_start', '>', $today)
            ->whereNotIn('status', [
                BookingStatus::Cancelled->value,
                BookingStatus::Completed->value,
                BookingStatus::NoShow->value,
            ])
            ->orderBy('event_start')
            ->limit($limit)
            ->get();
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return Collection<int, Quotation>
     */
    public function expiringQuotations(array $filters = [], int $limit = 10): Collection
    {
        $today = PropertyDate::now()->startOfDay();
        $horizon = PropertyDate::now()->addDays(7)->endOfDay();

        return Quotation::query()
            ->with(['customer', 'booking'])
            ->where('status', QuotationStatus::Sent)
            ->whereBetween('expires_at', [$today, $horizon])
            ->when(! empty($filters['date_from']), fn (Builder $q) => $q->whereDate('expires_at', '>=', $filters['date_from']))
            ->when(! empty($filters['date_to']), fn (Builder $q) => $q->whereDate('expires_at', '<=', $filters['date_to']))
            ->orderBy('expires_at')
            ->limit($limit)
            ->get();
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return Collection<int, BookingSpace>
     */
    public function expiringHolds(array $filters = [], int $limit = 10): Collection
    {
        $now = PropertyDate::now();
        $horizon = PropertyDate::now()->addHours(48);

        return BookingSpace::query()
            ->with(['booking.customer', 'space'])
            ->where('reservation_status', ReservationStatus::Provisional)
            ->whereNotNull('hold_expires_at')
            ->whereBetween('hold_expires_at', [$now, $horizon])
            ->when(! empty($filters['space_id']), fn (Builder $q) => $q->where('space_id', $filters['space_id']))
            ->whereHas('booking', fn (Builder $q) => $this->applyBookingRelationFilters($q, $filters))
            ->orderBy('hold_expires_at')
            ->limit($limit)
            ->get();
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return Collection<int, Invoice>
     */
    public function dueInvoices(array $filters = [], int $limit = 10): Collection
    {
        $today = PropertyDate::now()->startOfDay();
        $horizon = PropertyDate::now()->addDays(7)->endOfDay();

        return $this->filteredInvoices($filters)
            ->with(['customer', 'booking'])
            ->whereNotIn('status', [InvoiceStatus::Draft, InvoiceStatus::Void, InvoiceStatus::Paid, InvoiceStatus::Refunded])
            ->where('outstanding_balance', '>', 0)
            ->where(function (Builder $query) use ($today, $horizon): void {
                $query->where('due_date', '<', $today)
                    ->orWhereBetween('due_date', [$today, $horizon]);
            })
            ->orderBy('due_date')
            ->limit($limit)
            ->get();
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return Collection<int, Payment>
     */
    public function recentPayments(array $filters = [], int $limit = 10): Collection
    {
        return $this->filteredPayments($filters)
            ->with(['customer', 'invoice'])
            ->where('status', PaymentStatus::Posted)
            ->latest('payment_date')
            ->limit($limit)
            ->get();
    }

    /**
     * @param  array<string, mixed>  $filters
     */
    public function reservationFilter(string $view, array $filters = []): Collection
    {
        $today = PropertyDate::now()->startOfDay();

        $query = $this->filteredBookings($filters)
            ->with(['customer', 'coordinator', 'eventType']);

        $requiresIssuedInvoice = in_array($view, ['upcoming', 'today', 'past'], true);

        if ($requiresIssuedInvoice) {
            $query->hasIssuedInvoice();
        }

        match ($view) {
            'upcoming' => $query
                ->where('event_start', '>=', $today)
                ->whereNotIn('status', [
                    BookingStatus::Cancelled->value,
                    BookingStatus::Completed->value,
                    BookingStatus::NoShow->value,
                ]),
            'today' => $query
                ->whereDate('event_start', $today)
                ->whereNotIn('status', [
                    BookingStatus::Cancelled->value,
                    BookingStatus::Completed->value,
                    BookingStatus::NoShow->value,
                ]),
            'past' => $query->where('event_end', '<', $today),
            'provisional' => $query->where('status', BookingStatus::Provisional),
            'confirmed' => $query->where('status', BookingStatus::Confirmed),
            'cancelled' => $query->where('status', BookingStatus::Cancelled),
            default => null,
        };

        return $query->orderBy('event_start')->get();
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return Builder<Booking>
     */
    private function filteredBookings(array $filters): Builder
    {
        $query = Booking::query()->whereNull('deleted_at');

        $this->applyBookingFilters($query, $filters);

        return $query;
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return Builder<Invoice>
     */
    private function filteredInvoices(array $filters): Builder
    {
        return Invoice::query()
            ->whereNull('deleted_at')
            ->when(! empty($filters['date_from']), fn (Builder $q) => $q->whereDate('invoice_date', '>=', $filters['date_from']))
            ->when(! empty($filters['date_to']), fn (Builder $q) => $q->whereDate('invoice_date', '<=', $filters['date_to']))
            ->when(! empty($filters['space_id']), function (Builder $q) use ($filters): void {
                $q->whereHas('booking.spaces', fn (Builder $inner) => $inner->where('space_id', $filters['space_id']));
            })
            ->when(! empty($filters['coordinator_id']), function (Builder $q) use ($filters): void {
                $q->whereHas('booking', fn (Builder $inner) => $inner->where('coordinator_id', $filters['coordinator_id']));
            });
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return Builder<Payment>
     */
    private function filteredPayments(array $filters): Builder
    {
        return Payment::query()
            ->when(! empty($filters['date_from']), fn (Builder $q) => $q->whereDate('payment_date', '>=', $filters['date_from']))
            ->when(! empty($filters['date_to']), fn (Builder $q) => $q->whereDate('payment_date', '<=', $filters['date_to']))
            ->when(! empty($filters['coordinator_id']), function (Builder $q) use ($filters): void {
                $q->whereHas('booking', fn (Builder $inner) => $inner->where('coordinator_id', $filters['coordinator_id']));
            });
    }

    /**
     * @param  Builder<Booking>  $query
     * @param  array<string, mixed>  $filters
     */
    private function applyBookingFilters(Builder $query, array $filters): void
    {
        $query
            ->when(! empty($filters['date_from']), fn (Builder $q) => $q->whereDate('event_start', '>=', $filters['date_from']))
            ->when(! empty($filters['date_to']), fn (Builder $q) => $q->whereDate('event_start', '<=', $filters['date_to']))
            ->when(! empty($filters['event_type_id']), fn (Builder $q) => $q->where('event_type_id', $filters['event_type_id']))
            ->when(! empty($filters['booking_status']), fn (Builder $q) => $q->where('status', $filters['booking_status']))
            ->when(! empty($filters['coordinator_id']), fn (Builder $q) => $q->where('coordinator_id', $filters['coordinator_id']))
            ->when(! empty($filters['space_id']), function (Builder $q) use ($filters): void {
                $q->whereHas('spaces', fn (Builder $inner) => $inner->where('space_id', $filters['space_id']));
            });

        if (! empty($filters['payment_status'])) {
            match ($filters['payment_status']) {
                'paid' => $query->where('grand_total', '>', 0)->where('outstanding_balance', '<=', 0),
                'partially_paid' => $query->where('amount_paid', '>', 0)->where('outstanding_balance', '>', 0),
                'unpaid' => $query->where('outstanding_balance', '>', 0)->where('amount_paid', '<=', 0),
                default => null,
            };
        }
    }

    /**
     * @param  Builder<Booking>  $query
     * @param  array<string, mixed>  $filters
     */
    private function applyBookingRelationFilters(Builder $query, array $filters): void
    {
        $query->whereNull('deleted_at')
            ->whereNotIn('status', [BookingStatus::Cancelled, BookingStatus::NoShow]);

        $this->applyBookingFilters($query, $filters);
    }
}
