<?php

namespace App\Services;

use App\Models\ServicePackage;
use App\Models\Client;
use Illuminate\Support\Arr;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class ServicePackageService
{
    public function getAll(): Collection
    {
        return ServicePackage::query()
            ->with(['items.schedule', 'provider.user'])
            ->orderBy('created_at', 'desc')
            ->get();
    }

    public function findById(int $id): ?ServicePackage
    {
        return ServicePackage::query()
            ->with(['items.schedule', 'provider.user'])
            ->find($id);
    }

    public function create(array $data): ServicePackage
    {
        $model = ServicePackage::create($data);

        return $model->fresh(['provider.user']);
    }

    public function update(int $id, array $data): ?ServicePackage
    {
        $model = $this->findById($id);

        if (! $model) {
            return null;
        }

        if (data_get($data, 'provider_id') !== null) {
            $hasDifferentProvider = $model->items()
                ->whereHas('schedule', fn ($query) => $query->where('provider_id', '!=', data_get($data, 'provider_id')))
                ->exists();

            if ($hasDifferentProvider) {
                throw ValidationException::withMessages([
                    'provider_id' => __('messages.service_package_provider_mismatch'),
                ]);
            }
        }

        if (data_get($data, 'client_id') !== null) {
            $hasDifferentClient = $model->items()
                ->whereHas('schedule', fn ($query) => $query->where('client_id', '!=', data_get($data, 'client_id')))
                ->exists();

            if ($hasDifferentClient) {
                throw ValidationException::withMessages([
                    'client_id' => __('messages.service_package_client_mismatch'),
                ]);
            }
        }

        $model->update($data);

        return $model->fresh(['items.schedule', 'provider.user']);
    }

    public function delete(int $id): bool
    {
        $model = $this->findById($id);

        if (! $model) {
            return false;
        }

        return $model->delete();
    }

    //

    public function getByUserId(int $userId): Collection
    {
        return ServicePackage::query()
            ->with(['items.schedule.client.user', 'items.schedule.provider.user', 'items.schedule.address', 'provider.user'])
            ->whereHas('client', fn ($query) => $query->where('user_id', $userId))
            ->orderBy('created_at', 'desc')
            ->get();
    }

    public function getByProviderUserId(int $userId): Collection
    {
        return ServicePackage::query()
            ->with(['items.schedule.client.user', 'items.schedule.provider.user', 'items.schedule.address', 'provider.user'])
            ->whereHas('provider', fn ($query) => $query->where('user_id', $userId))
            ->orderBy('created_at', 'desc')
            ->get();
    }

    //

    public function acceptByProvider(int $id, int $userId): ServicePackage
    {
        $package = ServicePackage::query()
            ->with(['items.schedule'])
            ->whereHas('provider', fn ($query) => $query->where('user_id', $userId))
            ->findOrFail($id);

        if ($package->status->value !== 'open') {
            throw ValidationException::withMessages([
                'status' => __('messages.service_package_must_be_open_for_changes'),
            ]);
        }

        $scheduleService = app(ScheduleService::class);

        DB::transaction(function () use ($package, $scheduleService) {
            foreach ($package->items as $item) {
                if ($item->schedule && $item->schedule->status === 'pending') {
                    $scheduleService->updateStatus($item->schedule->id, 'accepted', fromPackage: true);
                }
            }
        });

        return $package->fresh(['items.schedule.client.user', 'items.schedule.provider.user', 'items.schedule.address', 'provider.user']);
    }

    public function refuseByProvider(int $id, int $userId): ServicePackage
    {
        $package = ServicePackage::query()
            ->with(['items.schedule'])
            ->whereHas('provider', fn ($query) => $query->where('user_id', $userId))
            ->findOrFail($id);

        if ($package->status->value !== 'open') {
            throw ValidationException::withMessages([
                'status' => __('messages.service_package_must_be_open_for_changes'),
            ]);
        }

        $scheduleService = app(ScheduleService::class);

        DB::transaction(function () use ($package, $scheduleService) {
            foreach ($package->items as $item) {
                if ($item->schedule && $item->schedule->status === 'pending') {
                    $scheduleService->updateStatus($item->schedule->id, 'rejected', fromPackage: true);
                }
            }

            $package->update(['status' => 'cancelled']);
        });

        return $package->fresh(['items.schedule.client.user', 'items.schedule.provider.user', 'items.schedule.address', 'provider.user']);
    }

    //

    public function createForUser(array $data, int $userId): ServicePackage
    {
        $client = Client::query()
            ->where('user_id', $userId)
            ->firstOrFail();

        $data['client_id'] = $client->id;

        return $this->hasSchedulePayload($data)
            ? $this->createWithSchedules($data)
            : $this->create($data);
    }

    public function createWithSchedules(array $data): ServicePackage
    {
        return DB::transaction(function () use ($data) {
            $items = $this->scheduleItems($data);

            $providerId = $this->singleProviderId($data, $items);

            $schedules = app(ScheduleService::class)->createSingleOrMultiple(
                baseData:  $this->baseScheduleData($data),
                schedules: $items,
            );

            $servicePackage = ServicePackage::create([
                'client_id'   => data_get($data, 'client_id'),
                'provider_id' => $providerId,
            ]);

            foreach ($schedules as $schedule) {
                $servicePackage->items()->create([
                    'schedule_id' => $schedule->id,
                ]);
            }

            return $servicePackage->fresh(['items.schedule.client.user', 'items.schedule.provider.user', 'items.schedule.address', 'provider.user']);
        });
    }

    //

    private function baseScheduleData(array $data): array
    {
        return Arr::except($data, [
            'schedules',
            'dates',
            'date',
            'period_type',
            'start_time',
            'end_time',
            'total_amount',
            'offers_meal',
        ]);
    }

    private function hasSchedulePayload(array $data): bool
    {
        return ! empty(data_get($data, 'schedules'))
            || ! empty(data_get($data, 'dates'))
            || ! empty(data_get($data, 'date'));
    }

    private function scheduleItems(array $data): array
    {
        if (! empty(data_get($data, 'schedules'))) {
            return data_get($data, 'schedules');
        }

        if (! empty(data_get($data, 'dates'))) {
            return array_map(
                fn (string $date) => array_merge(
                    Arr::only($data, ['period_type', 'start_time', 'end_time', 'total_amount', 'offers_meal']),
                    ['date' => $date],
                ),
                data_get($data, 'dates'),
            );
        }

        return [
            Arr::only($data, ['date', 'period_type', 'start_time', 'end_time', 'total_amount', 'offers_meal']),
        ];
    }

    private function singleProviderId(array $data, array $items): int
    {
        $rootProviderId = data_get($data, 'provider_id');

        $providerIds = collect($items)
            ->map(fn (array $item) => data_get($item, 'provider_id', $rootProviderId))
            ->filter()
            ->unique()
            ->values();

        if ($providerIds->count() !== 1) {
            throw ValidationException::withMessages([
                'schedules' => __('messages.service_package_same_provider'),
            ]);
        }

        return (int) $providerIds->first();
    }
}
