<?php

declare(strict_types=1);

namespace Database\Seeders;

use App\Enums\BookingStatus;
use App\Enums\BookingType;
use App\Enums\SessionType;
use App\Enums\UserRole;
use App\Exceptions\SlotUnavailableException;
use App\Models\Booking;
use App\Models\Engineer;
use App\Models\Experience;
use App\Models\Package;
use App\Models\PackagePurchase;
use App\Models\Room;
use App\Models\RoomAddon;
use App\Models\User;
use App\Services\Booking\BookingService;
use Carbon\CarbonImmutable;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;

/**
 * Enough realistic traffic to see whether the admin panel actually works.
 *
 * Every booking is created through BookingService rather than inserted
 * directly, so the demo data obeys the same rules as production — including
 * the slot claims. If a seeded booking would collide, it is skipped rather
 * than forced, which is itself a useful signal.
 *
 * Local use only. Never run this against a live database.
 */
class DemoSeeder extends Seeder
{
    public function run(): void
    {
        $bookings = app(BookingService::class);
        $now = CarbonImmutable::now('UTC');

        $admin = User::updateOrCreate(
            ['email' => 'admin@mithstudios.test'],
            [
                'name' => 'Studio Admin',
                'password' => Hash::make('password'),
                'role' => UserRole::Admin,
                'email_verified_at' => $now,
            ],
        );
        $admin->profile()->updateOrCreate([], ['artist_name' => 'MITH']);

        $artists = [
            ['Drake', '07412 345678', '1996-10-24'],
            ['Shay', '07700 900111', '2004-02-11'],
            ['Jay Kay', '07700 900222', '1999-07-30'],
            ['Nova', '07700 900333', '2008-05-19'],
        ];

        $users = [];

        foreach ($artists as [$name, $phone, $dob]) {
            $user = User::updateOrCreate(
                ['email' => str($name)->slug().'@example.test'],
                ['name' => $name, 'password' => Hash::make('password'), 'role' => UserRole::Artist],
            );

            $user->profile()->updateOrCreate([], [
                'artist_name' => $name,
                'phone' => $phone,
                'date_of_birth' => $dob,
                'bio' => 'Seeded demo artist.',
            ]);

            $users[$name] = $user->fresh();
        }

        // Shay holds a Label Ready package: priority booking, two staff.
        $labelReady = $this->grantPackage($users['Shay'], 'label-ready', $now);

        $studioA = Room::where('slug', 'studio-a')->firstOrFail();
        $studioC = Room::where('slug', 'studio-c')->firstOrFail();
        $studioD = Room::where('slug', 'studio-d')->firstOrFail();
        $camera = RoomAddon::where('slug', 'camera')->first();

        // Two days out, so every seeded booking clears the 24 hour lead time
        // the same way a real one has to. Anchoring on "tomorrow" puts the
        // morning slots inside the lead window and the service rightly
        // refuses them.
        $soon = $now->setTimezone(config('studio.timezone'))
            ->addDays(2)->startOfDay()->setTimezone('UTC');

        // Awaiting a decision: paid by transfer, screenshot uploaded.
        $this->attempt(fn () => $bookings->create(
            user: $users['Drake'],
            room: $studioA,
            startsAt: $soon->addHours(15),
            slotCount: 6,
            sessionType: SessionType::WithEngineer,
            now: $now,
        ))?->update(['status' => BookingStatus::PaymentSubmitted]);

        // Confirmed and staffed.
        $confirmed = $this->attempt(fn () => $bookings->create(
            user: $users['Jay Kay'],
            room: $studioA,
            startsAt: $soon->addHours(9),
            slotCount: 4,
            sessionType: SessionType::WithEngineer,
            now: $now,
        ));
        if ($confirmed) {
            $bookings->confirm($confirmed, $admin, $now);
            $confirmed->update(['assigned_engineer_id' => Engineer::first()?->id]);
        }

        // On a package — confirmed outright, nothing owed.
        $this->attempt(fn () => $bookings->create(
            user: $users['Shay'],
            room: $studioA,
            startsAt: $soon->addDay()->addHours(13),
            slotCount: 4,
            sessionType: SessionType::WithEngineer,
            package: $labelReady,
            now: $now,
        ));

        // Self-serve podcast with the camera add-on.
        $this->attempt(fn () => $bookings->create(
            user: $users['Nova'],
            room: $studioC,
            startsAt: $soon->addHours(11),
            slotCount: 4,
            sessionType: SessionType::WithoutEngineer,
            addons: $camera ? [['addon' => $camera]] : [],
            now: $now,
        ));

        // A birthday in Studio D, still unpaid so the countdown is visible.
        // Nine guests: £650 base plus two beyond the seven included.
        $birthday = Experience::where('slug', 'birthday-experience')->first();

        $this->attempt(fn () => $bookings->create(
            user: $users['Nova'],
            room: $studioD,
            startsAt: $soon->addDays(2)->addHours(9),
            slotCount: $birthday?->slotCount() ?? 10,
            type: BookingType::Birthday,
            guestCount: 9,
            experience: $birthday,
            now: $now,
        ));

        $this->seedTodaysSession($bookings, $users['Drake'], $studioA, $admin, $now);

        $this->command?->info('Demo data seeded. Log in as admin@mithstudios.test / password');
    }

    /**
     * One session later today, so the dashboard is not empty on a fresh seed.
     *
     * Created against a clock three days in the past — which is honest, not a
     * trick: a session happening this afternoon genuinely was booked days ago,
     * and that is the only way it could have cleared the 24 hour lead time.
     */
    private function seedTodaysSession(
        BookingService $bookings,
        User $artist,
        Room $room,
        User $admin,
        CarbonImmutable $now,
    ): void {
        $local = $now->setTimezone(config('studio.timezone'));

        // Start at the next clear slot boundary at least an hour out.
        $start = $local->addHour()->startOfHour()->addHour();

        if ($start->day !== $local->day) {
            $this->command?->warn('Too late in the day to seed a session for today.');

            return;
        }

        $booking = $this->attempt(fn () => $bookings->create(
            user: $artist,
            room: $room,
            startsAt: $start->setTimezone('UTC'),
            slotCount: 4,
            sessionType: SessionType::WithEngineer,
            now: $now->subDays(3),
        ));

        if ($booking) {
            $bookings->confirm($booking, $admin, $now);
            $booking->update(['assigned_engineer_id' => Engineer::skip(1)->first()?->id]);
        }
    }

    private function grantPackage(User $user, string $slug, CarbonImmutable $now): PackagePurchase
    {
        $package = Package::where('slug', $slug)->firstOrFail();
        $slotsPerHour = 60 / (int) config('studio.slot_minutes');

        return PackagePurchase::updateOrCreate(
            ['reference' => strtoupper($slug).'-'.$user->id],
            [
                'user_id' => $user->id,
                'package_id' => $package->id,
                'price_paid' => $package->price,
                'studio_hours_granted' => $package->studio_hours,
                'engineer_hours_granted' => $package->engineer_hours,
                'mixmaster_credits_granted' => $package->mixmaster_credits,
                'skips_lead_time' => $package->skips_lead_time,
                'staff_per_session' => $package->staff_per_session,
                'studio_slots_remaining' => (int) ($package->studio_hours * $slotsPerHour),
                'engineer_slots_remaining' => (int) ($package->engineer_hours * $slotsPerHour),
                'mixmaster_credits_remaining' => $package->mixmaster_credits,
                'status' => 'active',
                'activated_at' => $now,
                'expires_at' => $now->addDays($package->validity_days),
            ],
        );
    }

    /**
     * Re-running the seeder should not fall over on slots it already claimed.
     */
    private function attempt(callable $create): ?Booking
    {
        try {
            return $create();
        } catch (SlotUnavailableException $e) {
            $this->command?->warn('Skipped a demo booking: '.$e->getMessage());

            return null;
        }
    }
}
