<?php
declare(strict_types=1);

namespace App\Controller;

use App\Mailer\ContactMailer;
use Cake\Core\Configure;
use Cake\Http\Client;
use Cake\Http\Exception\NotFoundException;
use Cake\Http\Response;
use Cake\Log\Log;
use Cake\ORM\TableRegistry;
use Throwable;

class BookingsController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        // FormProtection signs form field NAMES to prevent hidden-field tampering.
        // Our booking form uses raw <input> tags (not $this->Form->control()), so the
        // helper never registers them with FormProtection's signature. Since these
        // are user-supplied values (name, email, phone, slot) anyway — a user is
        // expected to be able to change them — there's no security value in locking
        // the names; CSRF still protects the request itself.
        $this->FormProtection->setConfig('unlockedFields', [
            'slot_id',
            'customer_name',
            'customer_email',
            'customer_phone',
        ]);
    }

    public function index(): void
    {
        $teachers = $this->fetchTable('Teachers')->find()
            ->where(['is_active' => true])
            ->orderByAsc('sort_order')
            ->all();

        $this->set(compact('teachers'));
    }

    public function teacher(string $slug): ?Response
    {
        $teacher = $this->fetchTable('Teachers')->find()
            ->where(['slug' => $slug, 'is_active' => true])
            ->first();
        if (!$teacher) {
            throw new NotFoundException('Teacher not found.');
        }

        $slots = $this->fetchTable('AvailabilitySlots')->find()
            ->where([
                'teacher_id' => $teacher->id,
                'is_booked' => false,
                'starts_at >=' => date('Y-m-d H:i:s'),
            ])
            ->orderByAsc('starts_at')
            ->limit(60)
            ->all();

        // Group slots by day
        $byDay = [];
        foreach ($slots as $s) {
            $key = $s->starts_at->format('Y-m-d');
            $byDay[$key][] = $s;
        }

        $this->set(compact('teacher', 'byDay'));
        return null;
    }

    public function checkout(): ?Response
    {
        $this->request->allowMethod(['post']);
        $slotId = (int)$this->request->getData('slot_id');
        $name = trim((string)$this->request->getData('customer_name'));
        $email = trim((string)$this->request->getData('customer_email'));
        $phone = trim((string)$this->request->getData('customer_phone', ''));

        if ($slotId <= 0 || $name === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $this->Flash->error('Please complete your name, email, and pick a time.');
            return $this->redirect($this->referer(['_name' => 'booking_index']));
        }

        $slotsTable = $this->fetchTable('AvailabilitySlots');
        $slot = $slotsTable->find()
            ->where(['AvailabilitySlots.id' => $slotId, 'AvailabilitySlots.is_booked' => false])
            ->contain(['Teachers'])
            ->first();
        if (!$slot) {
            $this->Flash->error('That slot is no longer available.');
            return $this->redirect(['_name' => 'booking_index']);
        }
        $teacher = $slot->teacher;

        $bookingsTable = $this->fetchTable('Bookings');
        $booking = $bookingsTable->newEntity([
            'slot_id' => $slot->id,
            'teacher_id' => $teacher->id,
            'customer_name' => $name,
            'customer_email' => $email,
            'customer_phone' => $phone ?: null,
            'amount_cents' => (int)($teacher->hourly_rate_cents ?? 6500),
            'status' => 'pending',
        ]);
        if (!$bookingsTable->save($booking)) {
            $this->Flash->error('We could not create your booking. Please try again.');
            return $this->redirect(['_name' => 'booking_index']);
        }

        $stripeSecret = (string)Configure::read('Stripe.secretKey');
        $successUrl = $this->buildAbsolute(['_name' => 'booking_success', '?' => ['session_id' => '{CHECKOUT_SESSION_ID}', 'b' => $booking->id]]);
        $cancelUrl = $this->buildAbsolute(['_name' => 'booking_cancel', '?' => ['b' => $booking->id]]);

        if ($stripeSecret !== '') {
            try {
                $sessionUrl = $this->createStripeSession($stripeSecret, $booking, $teacher, $slot, $successUrl, $cancelUrl);
                if ($sessionUrl !== null) {
                    return $this->redirect($sessionUrl);
                }
            } catch (Throwable $e) {
                Log::error('Stripe checkout failed: ' . $e->getMessage());
                $this->Flash->error('Payment service temporarily unavailable: ' . $e->getMessage());
                return $this->redirect(['_name' => 'booking_index']);
            }
        }

        // No Stripe configured — DEV-only fallback so the flow is testable end-to-end.
        if (Configure::read('debug')) {
            $booking->status = 'paid';
            $bookingsTable->save($booking);
            $slot->is_booked = true;
            $slotsTable->save($slot);
            $this->sendBookingConfirmation($booking->id);
            return $this->redirect(['_name' => 'booking_success', '?' => ['b' => $booking->id, 'demo' => 1]]);
        }

        $this->Flash->error('Payment is not configured. Please contact us to book.');
        return $this->redirect(['_name' => 'booking_index']);
    }

    public function success(): ?Response
    {
        $bookingId = (int)$this->request->getQuery('b');
        $sessionId = trim((string)$this->request->getQuery('session_id'));
        $isDemo = (bool)$this->request->getQuery('demo');

        $bookingsTable = $this->fetchTable('Bookings');
        $booking = $bookingsTable->find()->where(['Bookings.id' => $bookingId])->contain(['Teachers', 'AvailabilitySlots'])->first();

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

        // If we have a session id and the booking isn't yet paid, verify with Stripe.
        if ($sessionId !== '' && $booking->status !== 'paid') {
            $secret = (string)Configure::read('Stripe.secretKey');
            if ($secret !== '') {
                try {
                    $http = new Client(['timeout' => 10]);
                    $resp = $http->get('https://api.stripe.com/v1/checkout/sessions/' . urlencode($sessionId), [], [
                        'headers' => ['Authorization' => 'Bearer ' . $secret],
                    ]);
                    if ($resp->isOk()) {
                        $data = $resp->getJson();
                        if (isset($data['payment_status']) && $data['payment_status'] === 'paid') {
                            $booking->status = 'paid';
                            $booking->stripe_session_id = $sessionId;
                            $booking->stripe_payment_intent = (string)($data['payment_intent'] ?? '');
                            $bookingsTable->save($booking);

                            // Mark slot booked
                            $slot = $booking->slot;
                            if ($slot) {
                                $slot->is_booked = true;
                                $this->fetchTable('AvailabilitySlots')->save($slot);
                            }
                            $this->sendBookingConfirmation($booking->id);
                        }
                    }
                } catch (Throwable $e) {
                    Log::error('Stripe verification failed: ' . $e->getMessage());
                }
            }
        }

        $this->set(compact('booking', 'isDemo'));
        return null;
    }

    public function cancel(): void
    {
        $bookingId = (int)$this->request->getQuery('b');
        $bookingsTable = $this->fetchTable('Bookings');
        $booking = $bookingsTable->find()->where(['Bookings.id' => $bookingId])->first();
        if ($booking && $booking->status === 'pending') {
            $booking->status = 'cancelled';
            $bookingsTable->save($booking);
        }
        $this->set('booking', $booking);
    }

    private function sendBookingConfirmation(int $bookingId): void
    {
        try {
            $booking = $this->fetchTable('Bookings')->find()
                ->where(['Bookings.id' => $bookingId])
                ->contain(['Teachers', 'AvailabilitySlots'])
                ->first();
            if (!$booking) return;
            $mailer = new ContactMailer();
            $mailer->bookingConfirmation($booking)->deliver();
        } catch (Throwable $e) {
            Log::warning('Booking confirmation email failed: ' . $e->getMessage());
        }
    }

    private function createStripeSession(string $secret, $booking, $teacher, $slot, string $successUrl, string $cancelUrl): ?string
    {
        $startsHuman = $slot->starts_at->format('l j M · g:i A');
        $description = "Class on {$startsHuman} with {$teacher->full_name}";

        $body = [
            'mode' => 'payment',
            'success_url' => $successUrl,
            'cancel_url' => $cancelUrl,
            'customer_email' => $booking->customer_email,
            'client_reference_id' => 'BK-' . $booking->id,
            'line_items[0][quantity]' => 1,
            'line_items[0][price_data][currency]' => 'aud',
            'line_items[0][price_data][unit_amount]' => (int)$booking->amount_cents,
            'line_items[0][price_data][product_data][name]' => 'CandleCraft · ' . $teacher->full_name,
            'line_items[0][price_data][product_data][description]' => $description,
            'metadata[booking_id]' => (string)$booking->id,
            'metadata[slot_id]' => (string)$slot->id,
        ];

        $http = new Client(['timeout' => 12]);
        $resp = $http->post('https://api.stripe.com/v1/checkout/sessions', $body, [
            'headers' => [
                'Authorization' => 'Bearer ' . $secret,
                'Content-Type' => 'application/x-www-form-urlencoded',
            ],
        ]);

        $data = $resp->getJson();
        if (!$resp->isOk() || !is_array($data)) {
            $msg = is_array($data) && isset($data['error']['message']) ? $data['error']['message'] : 'Stripe responded ' . $resp->getStatusCode();
            throw new \RuntimeException($msg);
        }

        if (!empty($data['id'])) {
            $booking->stripe_session_id = (string)$data['id'];
            $this->fetchTable('Bookings')->save($booking);
        }

        return $data['url'] ?? null;
    }

    private function buildAbsolute(array $url): string
    {
        $uri = $this->request->getUri();
        $base = $uri->getScheme() . '://' . $uri->getHost()
            . ($uri->getPort() ? ':' . $uri->getPort() : '');
        // Router::url — not $this->Url (that's a view helper, not available in controllers).
        $path = \Cake\Routing\Router::url($url, false);
        // Stripe needs {CHECKOUT_SESSION_ID} raw (Router URL-encodes braces).
        $path = str_replace(['%7BCHECKOUT_SESSION_ID%7D', '%7B', '%7D'], ['{CHECKOUT_SESSION_ID}', '{', '}'], $path);
        return rtrim($base, '/') . $path;
    }
}
