<?php

declare(strict_types=1);
namespace Services;

use Helpers\Auth;
use Repositories\AddressRepository;
use Repositories\CartRepository;
use Repositories\OrderRepository;
use Repositories\ProductRepository;
use RuntimeException;

final class OrderService
{
    public function __construct(private ?OrderRepository $orders = null, private ?ProductRepository $products = null)
    {
        $this->orders ??= new OrderRepository();
        $this->products ??= new ProductRepository();
    }

    public function createCod(array $payload): array
    {
        $result = $this->createOrder($payload, 'cod', 'pending', 'confirmed', null, true);
        $user = Auth::user();
        if ($user) (new CartRepository())->clearUser((int)$user['id']);
        return $result;
    }

    public function createPendingOnline(array $payload): array
    {
        return $this->createOrder($payload, 'razorpay', 'pending', 'payment_pending', null, false);
    }

    private function createOrder(array $payload, string $paymentMethod, string $paymentStatus, string $orderStatus, ?string $razorpayOrderId, bool $deductStockImmediately): array
    {
        $user = Auth::user();
        $address = null;
        if ($user && !empty($payload['address_id'])) {
            $address = (new AddressRepository())->find((int)$user['id'], (int)$payload['address_id']);
            if (!$address) throw new RuntimeException('Selected address not found');
            $payload = array_merge($payload, [
                'customer_name' => $address['full_name'],
                'customer_phone' => $address['phone'],
                'address_line1' => $address['address_line1'],
                'address_line2' => $address['address_line2'] ?? null,
                'city' => $address['city'],
                'state' => $address['state'],
                'pincode' => $address['pincode'],
            ]);
        }

        $cartItems = $payload['items'] ?? [];
        if (!$cartItems && $user) {
            $cartItems = array_map(fn($i) => ['product_id' => (int)$i['product_id'], 'quantity' => (int)$i['quantity']], (new CartRepository())->get((int)$user['id'], ''));
        }
        if (!$cartItems) throw new RuntimeException('Cart is empty');

        $ids = array_values(array_unique(array_map(fn($i) => (int)$i['product_id'], $cartItems)));
        $products = $this->products->findManyByIds($ids);
        $map = array_column($products, null, 'id');
        $items = [];
        $subtotal = 0.0;

        foreach ($cartItems as $cartItem) {
            $productId = (int)($cartItem['product_id'] ?? 0);
            $qty = max(1, min(99, (int)($cartItem['quantity'] ?? 1)));
            if (!isset($map[$productId])) throw new RuntimeException('Invalid product in cart');
            $product = $map[$productId];
            if ((int)$product['stock_quantity'] < $qty) throw new RuntimeException($product['name'] . ' is out of stock');
            $unitPrice = (float)$product['price'];
            $lineTotal = $unitPrice * $qty;
            $subtotal += $lineTotal;
            $items[] = ['product_id'=>$productId,'product_name'=>$product['name'],'product_slug'=>$product['slug'],'quantity'=>$qty,'unit_price'=>$unitPrice,'total_price'=>$lineTotal];
        }

        $zone = $this->deliveryZone((string)($payload['pincode'] ?? ''));
        if (!$zone || !(int)$zone['is_serviceable']) {
            throw new RuntimeException('Delivery is not serviceable for selected PIN code');
        }
        $shipping = (float)$zone['delivery_charge'];
        $discount = 0.0;
        $tax = 0.0;
        $total = max(0, $subtotal + $shipping + $tax - $discount);
        $estimateDays = (int)$zone['delivery_estimate_days'];
        $estimateDate = date('Y-m-d', strtotime('+' . $estimateDays . ' days'));
        $deliveryInfo = [
            'serviceable' => true,
            'pincode' => (string)$zone['pincode'],
            'city' => (string)$zone['city'],
            'state' => (string)$zone['state'],
            'country' => (string)($zone['country'] ?? 'India'),
            'delivery_charge' => $shipping,
            'delivery_estimate_days' => $estimateDays,
            'estimated_delivery_date' => $estimateDate,
            'courier_partner' => null,
            'tracking_number' => null,
            'shipment_status' => null,
        ];
        $orderNumber = $this->generateOrderNumber();
        $snapshot = $address ?: [
            'full_name' => trim((string)$payload['customer_name']),
            'phone' => trim((string)$payload['customer_phone']),
            'address_line1' => trim((string)$payload['address_line1']),
            'address_line2' => !empty($payload['address_line2']) ? trim((string)$payload['address_line2']) : null,
            'pincode' => trim((string)$payload['pincode']),
            'city' => trim((string)$payload['city']),
            'state' => trim((string)$payload['state']),
            'country' => trim((string)($payload['country'] ?? 'India')),
        ];

        $order = [
            'user_id' => $user ? (int)$user['id'] : null,
            'order_number' => $orderNumber,
            'customer_name' => trim((string)$payload['customer_name']),
            'customer_email' => !empty($payload['customer_email']) ? trim((string)$payload['customer_email']) : ($user['email'] ?? null),
            'customer_phone' => trim((string)$payload['customer_phone']),
            'address_id' => !empty($payload['address_id']) ? (int)$payload['address_id'] : null,
            'address_line1' => trim((string)$payload['address_line1']),
            'address_line2' => !empty($payload['address_line2']) ? trim((string)$payload['address_line2']) : null,
            'city' => trim((string)$payload['city']),
            'state' => trim((string)$payload['state']),
            'pincode' => trim((string)$payload['pincode']),
            'address_snapshot' => json_encode($snapshot, JSON_UNESCAPED_UNICODE),
            'payment_method' => $paymentMethod,
            'payment_status' => $paymentStatus,
            'order_status' => $orderStatus,
            'subtotal' => $subtotal,
            'shipping_amount' => $shipping,
            'discount_amount' => $discount,
            'tax_amount' => $tax,
            'total_amount' => $total,
            'delivery_estimate_date' => $estimateDate,
            'notes' => !empty($payload['notes']) ? trim((string)$payload['notes']) : null,
            'razorpay_order_id' => $razorpayOrderId,
            'razorpay_payment_id' => null
        ];
        $id = $this->orders->create($order, $items, $deductStockImmediately);
        return ['order_id'=>$id,'order_number'=>$orderNumber,'subtotal'=>$subtotal,'shipping_amount'=>$shipping,'tax_amount'=>$tax,'discount_amount'=>$discount,'total_amount'=>$total,'payment_method'=>$paymentMethod,'payment_status'=>$paymentStatus,'order_status'=>$orderStatus,'delivery_estimate_date'=>$estimateDate,'delivery'=>$deliveryInfo];
    }

    public function attachRazorpayOrderId(string $orderNumber, string $razorpayOrderId): void
    {
        $this->orders->attachRazorpayOrderId($orderNumber, $razorpayOrderId);
    }

    public function markPaid(string $orderNumber, string $razorpayOrderId, string $paymentId, float $paidAmount, ?string $signature = null, array $rawPayment = []): void
    {
        $this->orders->confirmPaidAndDeductStock($orderNumber, $razorpayOrderId, $paymentId, $paidAmount, $signature, $rawPayment);
    }

    public function markFailed(string $orderNumber): void
    {
        $this->orders->markPaymentFailed($orderNumber);
    }

    private function generateOrderNumber(): string
    {
        return 'AFP' . date('YmdHis') . random_int(100, 999);
    }

    /**
     * Pan-India fallback tiers, keyed by PIN prefix. Explicit rows in
     * delivery_zones always take priority over this table. Origin: Nashik, Maharashtra.
     * Kept in sync with Controllers\CheckoutController::fallbackZone().
     */
    private function fallbackZone(string $pin): array
    {
        $p2 = substr($pin, 0, 2);
        $p1 = substr($pin, 0, 1);

        $remotePrefixes2 = ['78', '79', '17', '18', '19'];
        if (in_array($p2, $remotePrefixes2, true) || substr($pin, 0, 3) === '744') {
            return ['city' => 'Remote/NE Region', 'state' => 'Remote Zone', 'delivery_charge' => 150, 'delivery_estimate_days' => 8];
        }

        if ($p1 === '4' && $pin >= '400000' && $pin <= '445999') {
            return ['city' => 'Maharashtra', 'state' => 'Maharashtra', 'delivery_charge' => 40, 'delivery_estimate_days' => 2];
        }

        $nearbyPrefixes2 = ['36', '37', '38', '39', '45', '46', '47', '48', '40', '56', '57', '58', '59', '50', '51', '52', '53'];
        if (in_array($p2, $nearbyPrefixes2, true)) {
            return ['city' => 'Nearby State', 'state' => 'Nearby Zone', 'delivery_charge' => 70, 'delivery_estimate_days' => 4];
        }

        return ['city' => 'India', 'state' => 'Rest of India', 'delivery_charge' => 100, 'delivery_estimate_days' => 6];
    }

    private function deliveryZone(string $pincode): ?array
    {
        if ($pincode === '') return null;
        $db = \Config\Database::connection();
        $stmt = $db->prepare('SELECT * FROM delivery_zones WHERE pincode=:p LIMIT 1');
        $stmt->execute(['p' => $pincode]);
        $row = $stmt->fetch();

        if ($row) {
            return (int)$row['is_serviceable'] ? $row : null; // explicit row: respect admin block
        }

        if (!preg_match('/^\d{6}$/', $pincode)) return null; // invalid format, never fallback

        $fallback = $this->fallbackZone($pincode);
        return [
            'pincode' => $pincode,
            'city' => $fallback['city'],
            'state' => $fallback['state'],
            'country' => 'India',
            'is_serviceable' => 1,
            'delivery_charge' => $fallback['delivery_charge'],
            'delivery_estimate_days' => $fallback['delivery_estimate_days'],
        ];
    }
}