<?php

namespace App\Livewire\Forms\Studio\Master;

use App\Enums\Day;
use App\Enums\OutletStatus;
use App\Models\OpeningHour;
use App\Models\Outlet;
use App\Models\User;
use App\Rules\PhoneNumber;
use App\Traits\Media\WithMediaHandler;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Livewire\Form;

class OutletForm extends Form
{
    use WithMediaHandler;

    public ?Outlet $outlet = null;

    public string $name = '';

    public string $phone_number = '';

    public string $address = '';

    public ?string $landmark = null;

    public ?string $maps_url = null;

    public array $opening_hours = [];

    public array $facilities = ['Parkir'];

    public string $opened_date = '';

    public int $status = OutletStatus::OPERATIONAL->value;

    public array $featured_image = [];

    public array $images = [];

    public function rules(): array
    {
        return array_merge($this->getBasicRules(), $this->getOpeningHoursRules());
    }

    private function getBasicRules(): array
    {
        return [
            'name' => ['required', 'string', 'max:100'],
            'phone_number' => ['required', 'string', new PhoneNumber],
            'address' => ['required', 'string'],
            'landmark' => ['required', 'string', 'max:30'],
            'maps_url' => [
                'nullable',
                'string',
                'regex:/^(https?:\/\/)?(www\.)?(google\.[a-z.]+\/maps\/.+|goo\.gl\/maps\/.+|maps\.app\.goo\.gl\/.+)$/i',
            ],
            'opening_hours' => ['required', 'array'],
            'facilities' => ['required', 'array', 'min:1'],
            'facilities.*' => ['required', 'string', 'max:20'],
            'opened_date' => ['required', 'date'],
            'status' => ['required', Rule::in(OutletStatus::cases())],
            'featured_image' => ['required', 'array', 'max:1'],
            'images' => ['required', 'array', 'max:5'],
        ];
    }

    private function getOpeningHoursRules(): array
    {
        $rules = [];

        foreach (Day::cases() as $day) {
            $rules["opening_hours.{$day->value}"] = ['required', 'array'];
            $rules["opening_hours.{$day->value}.open_time"] = ['nullable', 'date_format:H:i'];
            $rules["opening_hours.{$day->value}.close_time"] = ['nullable', 'date_format:H:i'];
        }

        return $rules;
    }

    public function validationAttributes(): array
    {
        return [
            'name' => 'nama',
            'phone_number' => 'nomor telepon',
            'address' => 'alamat',
            'landmark' => 'patokan',
            'maps_url' => 'tautan google maps',
            'opening_hours' => 'jam beroperasi',
            'opening_hours.*' => 'jam beroperasi',
            'facilities' => 'fasilitas',
            'facilities.*' => 'fasilitas',
            'opened_date' => 'tanggal buka',
            'featured_image' => 'gambar utama',
            'images' => 'gambar',
        ];
    }

    public function setOutlet(Outlet $outlet): void
    {
        $outlet->load(['openingHours', 'facilities']);

        $this->outlet = $outlet;

        $this->fillBasicAttributes($outlet);
        $this->opening_hours = $this->formatOpeningHoursForForm($outlet);
        $this->facilities = $outlet->facilities->pluck('name')->toArray();
        $this->featured_image = $this->mapMediaCollection($outlet->getMedia('featured_image'));
        $this->images = $this->mapMediaCollection($outlet->getMedia('images'));
    }

    private function fillBasicAttributes(Outlet $outlet): void
    {
        $this->name = $outlet->name;
        $this->phone_number = $outlet->phone_number;
        $this->address = $outlet->address;
        $this->landmark = $outlet->landmark;
        $this->maps_url = $outlet->maps_url;
        $this->opened_date = $outlet->opened_date;
        $this->status = $outlet->status->value;
    }

    private function formatOpeningHoursForForm(Outlet $outlet): array
    {
        return $outlet->openingHours
            ->mapWithKeys(fn ($item) => [
                $item->day->value => [
                    'open_time' => $item->open_time ? formatTime($item->open_time) : null,
                    'close_time' => $item->close_time ? formatTime($item->close_time) : null,
                ],
            ])
            ->toArray();
    }

    public function store(): void
    {
        $this->validate();

        $data = $this->prepareDataForSave();

        DB::transaction(function () use ($data) {
            $outlet = Outlet::create($data);

            $this->createOpeningHours($outlet, $data['opening_hours']);
            $this->createFacilities($outlet, $data['facilities']);
            $this->handleMediaUpload($outlet);

            // Auto-assign users with Developer, Owner, and Leader roles to the new outlet
            $this->assignUsersToOutlet($outlet);
        });
    }

    private function prepareDataForSave(): array
    {
        $data = $this->all();

        $data['closed_date'] = $data['status'] == OutletStatus::TERMINATED->value
            ? now()
            : null;

        return $data;
    }

    private function createOpeningHours(Outlet $outlet, array $openingHours): void
    {
        $hoursData = collect($openingHours)
            ->map(fn ($time, $day) => [
                'outlet_id' => $outlet->id,
                'day' => $day,
                'open_time' => $time['open_time'],
                'close_time' => $time['close_time'],
            ])
            ->values()
            ->toArray();

        OpeningHour::insert($hoursData);
    }

    private function createFacilities(Outlet $outlet, array $facilities): void
    {
        $facilitiesData = collect($facilities)
            ->map(fn ($facility) => ['name' => $facility])
            ->toArray();

        $outlet->facilities()->createMany($facilitiesData);
    }

    private function handleMediaUpload(Outlet $outlet): void
    {
        $this->uploadMedia($this->featured_image, $outlet, 'featured_image');
        $this->uploadMedia($this->images, $outlet, 'images');
    }

    private function assignUsersToOutlet(Outlet $outlet): void
    {
        // Get users with Developer, Owner, and Leader roles
        $users = User::whereHas('roles', function ($query) {
            $query->whereIn('name', ['Developer', 'Owner', 'Leader']);
        })->get();

        // Assign users to the outlet
        if ($users->isNotEmpty()) {
            $outlet->users()->attach($users->pluck('id'));
        }
    }

    public function update(): void
    {
        $this->validate();

        $data = $this->prepareDataForSave();

        DB::transaction(function () use ($data) {
            $this->outlet->update($data);

            $this->updateOpeningHours($data['opening_hours']);
            $this->updateFacilities($data['facilities']);
            $this->handleMediaSync($data);
            $this->handleMediaUpload($this->outlet);
        });
    }

    private function updateOpeningHours(array $openingHours): void
    {
        // Delete opening hours for days that are not in the new data
        $this->outlet->openingHours()
            ->whereNotIn('day', array_keys($openingHours))
            ->delete();

        // Add or update opening hours
        foreach ($openingHours as $day => $hours) {
            OpeningHour::updateOrCreate(
                [
                    'outlet_id' => $this->outlet->id,
                    'day' => $day,
                ],
                [
                    'open_time' => $hours['open_time'] ?? null,
                    'close_time' => $hours['close_time'] ?? null,
                ]
            );
        }
    }

    private function updateFacilities(array $facilities): void
    {
        // Delete facilities that are not in the new list
        $this->outlet->facilities()
            ->whereNotIn('name', $facilities)
            ->delete();

        // Add or update facilities
        foreach ($facilities as $facilityName) {
            $this->outlet->facilities()->updateOrCreate(
                ['name' => $facilityName],
                ['name' => $facilityName]
            );
        }
    }

    private function handleMediaSync(array $data): void
    {
        $this->syncMedia($data['featured_image'], $this->outlet, 'featured_image');
        $this->syncMedia($data['images'], $this->outlet, 'images');
    }
}
