<?php

namespace App\Services;

use App\Core\Database;
use Exception;
use PDO;

class BookingService
{
    private PDO $db;

    public function __construct()
    {
        $this->db = Database::connect();
    }

    public function createBooking(array $data): array
    {
        $this->db->beginTransaction();

        try {

            // ===============================
            // VALIDATION
            // ===============================
            $this->validateRequired($data);

            // ===============================
            // FETCH PRICING RULE
            // ===============================
            $rule = $this->getPriceRule($data['service_id']);

            // ===============================
            // APPLY AIRPORT RULES
            // ===============================
            $data = $this->applyAirportRules($data, $rule);

            // ===============================
            // CALCULATE PRICE
            // ===============================
            $finalPrice = $this->calculatePrice($data, $rule);

            // ===============================
            // CREATE / FETCH CUSTOMER
            // ===============================
            $customerId = $this->getOrCreateCustomer($data);

            // ===============================
            // GENERATE BOOKING REF
            // ===============================
            $bookingReference = $this->generateBookingReference();

            // ===============================
            // INSERT BOOKING
            // ===============================
            $bookingId = $this->insertBooking(
                $bookingReference,
                $customerId,
                $data,
                $finalPrice
            );

            // ===============================
            // INSERT TIMELINE
            // ===============================
            $this->insertTimeline($bookingId);

            $this->db->commit();

            return [
                'booking_reference' => $bookingReference,
                'final_price'       => number_format($finalPrice, 2),
                'service_name'      => $rule['service_name']
            ];

        } catch (Exception $e) {

            if ($this->db->inTransaction()) {
                $this->db->rollBack();
            }

            throw $e;
        }
    }

    private function validateRequired(array $data): void
    {
        if (
            empty($data['service_id']) ||
            empty($data['vehicle_type']) ||
            empty($data['pickup_address']) ||
            empty($data['pickup_datetime']) ||
            empty($data['name']) ||
            empty($data['email'])
        ) {
            throw new Exception('Missing required fields.');
        }
    }

    private function getPriceRule(int $serviceId): array
    {
        $stmt = $this->db->prepare("
            SELECT pr.*, s.service_name
            FROM price_rules pr
            JOIN services s ON s.id = pr.service_id
            WHERE pr.service_id = ? AND pr.is_active = 1
            LIMIT 1
        ");
        $stmt->execute([$serviceId]);

        $rule = $stmt->fetch();

        if (!$rule) {
            throw new Exception('Pricing rule not found.');
        }

        return $rule;
    }

    private function applyAirportRules(array $data, array $rule): array
    {
        if ($rule['airport_pickup_default']) {
            $data['pickup_address'] = "Changi Airport";
        }

        if ($rule['airport_dropoff_default']) {
            $data['dropoff_address'] = "Changi Airport";
        }

        if ($rule['flight_required'] && empty($data['flight_number'])) {
            throw new Exception('Flight number required.');
        }

        return $data;
    }

    private function calculatePrice(array $data, array $rule): float
    {
        $pricingType = $rule['pricing_type'];

        if ($pricingType === 'fixed') {
            return (float) $rule['base_price'];
        }

        if ($pricingType === 'zone') {

            if ($rule['zone_required'] && empty($data['zone_id'])) {
                throw new Exception('Pickup zone required.');
            }

            $stmt = $this->db->prepare("SELECT price_modifier FROM zones WHERE id = ?");
            $stmt->execute([$data['zone_id']]);
            $zone = $stmt->fetch();

            if (!$zone) {
                throw new Exception('Invalid zone.');
            }

            return (float) $zone['price_modifier'];
        }

        if ($pricingType === 'hourly') {

            if ($data['hours'] < $rule['minimum_hours']) {
                throw new Exception(
                    "Minimum booking is {$rule['minimum_hours']} hours."
                );
            }

            return (float) $rule['hourly_rate'] * $data['hours'];
        }

        throw new Exception('Invalid pricing type.');
    }

    private function getOrCreateCustomer(array $data): int
    {
        $stmt = $this->db->prepare("SELECT id FROM customers WHERE email = ?");
        $stmt->execute([$data['email']]);
        $customer = $stmt->fetch();

        if ($customer) {
            return $customer['id'];
        }

        $stmt = $this->db->prepare("
            INSERT INTO customers (name, phone, email, created_at)
            VALUES (?, ?, ?, NOW())
        ");
        $stmt->execute([$data['name'], $data['phone'], $data['email']]);

        return (int) $this->db->lastInsertId();
    }

    private function generateBookingReference(): string
    {
        return 'EXC-' . date('Ymd') . '-' . strtoupper(bin2hex(random_bytes(3)));
    }

    private function insertBooking(
        string $bookingReference,
        int $customerId,
        array $data,
        float $price
    ): int {

        $stmt = $this->db->prepare("
            INSERT INTO bookings (
                booking_reference,
                customer_id,
                service_id,
                pickup_address,
                dropoff_address,
                pickup_datetime,
                zone_id,
                vehicle_type,
                estimated_price,
                final_price,
                status,
                created_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', NOW())
        ");

        $stmt->execute([
            $bookingReference,
            $customerId,
            $data['service_id'],
            $data['pickup_address'],
            $data['dropoff_address'],
            $data['pickup_datetime'],
            !empty($data['zone_id']) ? (int)$data['zone_id'] : null,
            $data['vehicle_type'],
            $price,
            $price
        ]);

        return (int) $this->db->lastInsertId();
    }

    private function insertTimeline(int $bookingId): void
    {
        $stmt = $this->db->prepare("
            INSERT INTO booking_timeline
            (booking_id, status, updated_by)
            VALUES (?, 'pending', 'system')
        ");
        $stmt->execute([$bookingId]);
    }
}
