<?php

namespace App\Services;

use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class BookingService
{
    public function createFromQuote(int $cartId, int $quoteId, int $userId, ?string $notes = null): array
    {
        return DB::transaction(function () use ($cartId, $quoteId, $userId, $notes) {
            $cart = DB::table('carts')->where('id', $cartId)->where('user_id', $userId)->first();
            $quote = DB::table('quotes')
                ->where('id', $quoteId)
                ->where('cart_id', $cartId)
                ->where('user_id', $userId)
                ->where('status', 'active')
                ->where('expires_at', '>', now())
                ->first();

            if (! $cart || ! $quote) {
                throw new \RuntimeException('Active quote not found or quote expired.');
            }

            if (! $cart->address_id) {
                throw new \RuntimeException('Address is required before booking.');
            }

            $snapshot = json_decode($quote->pricing_snapshot_json, true) ?: [];
            $duration = (int) ($snapshot['duration_minutes'] ?? 60);
            $startAt = $cart->booking_type === 'instant'
                ? now()->addMinutes(15)
                : Carbon::parse($cart->scheduled_start_at ?: now()->addDay());
            $endAt = (clone $startAt)->addMinutes($duration);

            $bookingId = DB::table('bookings')->insertGetId([
                'booking_number' => 'BK-' . now()->format('Ymd') . '-' . strtoupper(Str::random(6)),
                'user_id' => $userId,
                'address_id' => $cart->address_id,
                'quote_id' => $quoteId,
                'booking_type' => $cart->booking_type,
                'scheduled_start_at' => $startAt,
                'scheduled_end_at' => $endAt,
                'status' => 'confirmed',
                'payment_status' => $quote->payable_total > 0 ? 'unpaid' : 'paid',
                'assignment_status' => 'unassigned',
                'subtotal' => $quote->subtotal,
                'fees_total' => $quote->fees_total,
                'discount_total' => $quote->discount_total,
                'tax_total' => $quote->tax_total,
                'payable_total' => $quote->payable_total,
                'customer_notes' => $notes,
                'created_at' => now(),
                'updated_at' => now(),
            ]);

            $items = DB::table('cart_items')
                ->join('services', 'services.id', '=', 'cart_items.service_id')
                ->where('cart_items.cart_id', $cartId)
                ->select('cart_items.*', 'services.name as service_name', 'services.default_duration_minutes')
                ->get();

            foreach ($items as $item) {
                DB::table('booking_items')->insert([
                    'booking_id' => $bookingId,
                    'service_id' => $item->service_id,
                    'variant_id' => $item->variant_id,
                    'service_name_snapshot' => $item->service_name,
                    'quantity' => $item->quantity,
                    'duration_minutes' => $item->duration_minutes ?: $item->default_duration_minutes,
                    'price_snapshot_json' => json_encode($snapshot),
                    'status' => 'pending',
                    'created_at' => now(),
                    'updated_at' => now(),
                ]);
            }

            DB::table('booking_status_histories')->insert([
                'booking_id' => $bookingId,
                'from_status' => null,
                'to_status' => 'confirmed',
                'actor_type' => 'customer',
                'actor_id' => $userId,
                'reason' => 'Booking created from active quote.',
                'metadata_json' => json_encode(['quote_id' => $quoteId]),
                'created_at' => now(),
                'updated_at' => now(),
            ]);

            DB::table('quotes')->where('id', $quoteId)->update(['status' => 'used', 'updated_at' => now()]);
            DB::table('carts')->where('id', $cartId)->update(['status' => 'converted', 'updated_at' => now()]);

            if ($cart->booking_type === 'recurring') {
                $this->createRecurringPlan($bookingId, $cart, $quote, $userId);
            }

            return $this->loadBooking($bookingId);
        });
    }

    public function changeStatus(int $bookingId, string $toStatus, string $actorType, ?int $actorId = null, ?string $reason = null): void
    {
        DB::transaction(function () use ($bookingId, $toStatus, $actorType, $actorId, $reason) {
            $booking = DB::table('bookings')->where('id', $bookingId)->lockForUpdate()->first();

            if (! $booking) {
                throw new \RuntimeException('Booking not found.');
            }

            DB::table('bookings')->where('id', $bookingId)->update([
                'status' => $toStatus,
                'completed_at' => $toStatus === 'completed' ? now() : $booking->completed_at,
                'updated_at' => now(),
            ]);

            DB::table('booking_status_histories')->insert([
                'booking_id' => $bookingId,
                'from_status' => $booking->status,
                'to_status' => $toStatus,
                'actor_type' => $actorType,
                'actor_id' => $actorId,
                'reason' => $reason,
                'created_at' => now(),
                'updated_at' => now(),
            ]);
        });
    }

    public function loadBooking(int $bookingId): array
    {
        $booking = (array) DB::table('bookings')->where('id', $bookingId)->first();
        $booking['items'] = DB::table('booking_items')->where('booking_id', $bookingId)->get();
        $booking['timeline'] = DB::table('booking_status_histories')->where('booking_id', $bookingId)->orderBy('id')->get();
        $booking['assignment'] = DB::table('booking_assignments')->where('booking_id', $bookingId)->latest('id')->first();

        return $booking;
    }

    private function createRecurringPlan(int $bookingId, object $cart, object $quote, int $userId): void
    {
        $recurring = json_decode($cart->recurring_json, true) ?: [];
        $startDate = $recurring['start_date'] ?? now()->toDateString();
        $endDate = $recurring['end_date'] ?? now()->addMonth()->toDateString();

        $planId = DB::table('recurring_plans')->insertGetId([
            'plan_number' => 'RP-' . now()->format('Ymd') . '-' . strtoupper(Str::random(6)),
            'user_id' => $userId,
            'address_id' => $cart->address_id,
            'quote_id' => $quote->id,
            'cadence' => $recurring['cadence'] ?? 'weekly',
            'days_of_week_json' => json_encode($recurring['days_of_week'] ?? []),
            'start_date' => $startDate,
            'end_date' => $endDate,
            'preferred_start_time' => Carbon::parse($cart->scheduled_start_at ?: now())->format('H:i:s'),
            'status' => 'active',
            'created_at' => now(),
            'updated_at' => now(),
        ]);

        DB::table('bookings')->where('id', $bookingId)->update([
            'recurring_plan_id' => $planId,
            'updated_at' => now(),
        ]);

        DB::table('recurring_sessions')->insert([
            'recurring_plan_id' => $planId,
            'booking_id' => $bookingId,
            'session_date' => Carbon::parse($startDate)->toDateString(),
            'scheduled_start_at' => $cart->scheduled_start_at ?: now(),
            'status' => 'generated',
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }
}
