<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Park;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class ParkService
{
    public function paginate(array $filters, int $perPage = 20): LengthAwarePaginator
    {
        $query = DB::table('park as p')
            ->leftJoin('city as c', 'p.city_id', '=', 'c.city_id')
            ->select([
                'p.*',
                'c.city_name',
            ]);

        if (!empty($filters['park_name'])) {
            $query->where('p.park_name', 'like', '%' . (string) $filters['park_name'] . '%');
        }

        if (!empty($filters['city_id'])) {
            $query->where('p.city_id', (int) $filters['city_id']);
        }

        $sort = (string) ($filters['sort'] ?? '');
        $sortType = (string) ($filters['sort_type'] ?? 'asc');

        if ($sort === '') {
            $sort = 'p.park_id';
        }

        $allowedSorts = [
            'p.park_id',
            'p.park_name',
            'p.city_id',
            'p.updated_at',
            'c.city_name',
        ];

        if (!in_array($sort, $allowedSorts, true)) {
            $sort = 'p.park_id';
        }

        if (!in_array($sortType, ['asc', 'desc'], true)) {
            $sortType = 'asc';
        }

        $query->orderBy($sort, $sortType);

        return $query->paginate($perPage)->appends([
            'park_name' => $filters['park_name'] ?? null,
            'city_id' => $filters['city_id'] ?? null,
            'sort' => $sort,
            'sort_type' => $sortType,
        ]);
    }

    public function findOrFail(int $parkId): Park
    {
        $park = Park::find($parkId);

        if (!$park) {
            throw new ModelNotFoundException();
        }

        return $park;
    }

    public function create(array $payload, int $operatorId): Park
    {
        return DB::transaction(function () use ($payload, $operatorId) {
            $park = new Park();
            $park->fill($payload);
            $park->operator_id = $operatorId;
            $park->save();

            return $park;
        });
    }

    public function update(Park $park, array $payload, int $operatorId): Park
    {
        return DB::transaction(function () use ($park, $payload, $operatorId) {
            $park->fill($payload);
            $park->operator_id = $operatorId;
            $park->save();

            return $park;
        });
    }

    public function deleteByIds(array $ids): bool
    {
        return (bool) Park::destroy($ids);
    }

    public function exportRows(): Collection
    {
        return DB::table('park as p')
            ->select([
                'p.park_id',
                'p.city_id',
                'p.park_name',
                'p.park_ruby',
                'p.park_syllabary',
                'p.park_adrs',
                'p.park_close_flag',
                'p.park_day',
                'p.alert_flag',
                'p.print_number',
                'p.keep_alive',
                'p.operator_id',
                'p.update_grace_period_start_date',
                'p.update_grace_period_start_time',
                'p.update_grace_period_end_date',
                'p.update_grace_period_end_time',
                'p.parking_start_grace_period',
                'p.reminder_type',
                'p.reminder_time',
                'p.immediate_use_permit',
                'p.gender_display_flag',
                'p.bd_display_flag',
                'p.securityreg_display_flag',
                'p.distance_twopoints',
                'p.park_latitude',
                'p.park_longitude',
                'p.park_tel',
                'p.park_fixed_contract',
                'p.park_temporary_contract',
                'p.park_restriction',
                'p.park_procedure',
                'p.park_payment',
                'p.park_available_time_flag',
                'p.park_available_time_from',
                'p.park_available_time_to',
                'p.park_manager_flag',
                'p.park_manager_resident_from',
                'p.park_manager_resident_to',
                'p.park_roof_flag',
                'p.park_issuing_machine_flag',
                'p.park_using_method',
                'p.park_contract_renewal_term',
                'p.park_reservation',
                'p.park_reference',
                'p.student_id_confirm_type',
                'p.reduction_guide_display_flag',
                'p.reduction_age',
                'p.reduction_guide_display_start_month',
                'p.overyear_flag',
            ])
            ->orderBy('p.park_id', 'asc')
            ->get();
    }

    public function importCsv(UploadedFile $file): void
    {
        $data = \App\Utils::csvToArray($file);

        DB::transaction(function () use ($data) {
            Park::query()->delete();

            $expectedCols = 13;

            foreach ($data as $index => $items) {
                if (count($items) !== $expectedCols) {
                    throw new \RuntimeException('列数が一致しません。line=' . ($index + 2));
                }

                $row = new Park();
                $row->park_id = $items[0];
                $row->city_id = $items[1];
                $row->park_name = $items[3];
                $row->park_ruby = $items[4];
                $row->park_syllabary = $items[5];
                $row->park_adrs = $items[6];
                $row->park_close_flag = $items[7];
                $row->park_day = $items[9];
                $row->alert_flag = $items[10];
                $row->print_number = $items[11];
                $row->keep_alive = $items[12];

                $row->save();
            }
        });
    }

    public function checkDuplicateByName(string $parkName): ?Park
    {
        return Park::where('park_name', $parkName)->first();
    }
}
