<?php

namespace App\Services;

use App\Models\DeliveryZone;
use App\Models\Location;
use App\Models\Supplier;
use App\Models\SupplierDeliveryRate;

class DeliveryCalculatorService
{
    /**
     * Calculate delivery cost based on distance and weight
     */
    public function calculateDeliveryCost(
        Supplier $supplier,
        string $destinationCity,
        ?string $destinationProvince = null,
        ?string $destinationPostalCode = null,
        float $totalWeight = 0,
        float $orderTotal = 0
    ): array {
        // Get supplier location
        $supplierLocation = $this->getSupplierLocation($supplier);
        
        // Get destination location
        $destinationLocation = $this->getDestinationLocation(
            $destinationCity,
            $destinationProvince,
            $destinationPostalCode
        );

        // Calculate distance
        $distance = $this->calculateDistance($supplierLocation, $destinationLocation);

        // Get applicable delivery zone
        $zone = DeliveryZone::getZoneForDistance($distance);

        if (!$zone) {
            return [
                'success' => false,
                'message' => 'Delivery not available to this location',
                'distance_km' => $distance,
            ];
        }

        // Check for supplier-specific rates
        $supplierRate = SupplierDeliveryRate::where('supplier_id', $supplier->id)
            ->where('delivery_zone_id', $zone->id)
            ->where('is_active', true)
            ->first();

        // Use supplier rates if available, otherwise use zone defaults
        $baseRate = $supplierRate?->base_rate ?? $zone->base_rate;
        $ratePerKm = $supplierRate?->rate_per_km ?? $zone->rate_per_km;
        $ratePerKg = $supplierRate?->rate_per_kg ?? $zone->rate_per_kg;
        $freeThreshold = $supplierRate?->free_delivery_threshold;

        // Check for free delivery
        if ($freeThreshold && $orderTotal >= $freeThreshold) {
            return [
                'success' => true,
                'delivery_cost' => 0,
                'distance_km' => $distance,
                'zone' => $zone->name,
                'estimated_days' => "{$zone->estimated_days_min}-{$zone->estimated_days_max}",
                'free_delivery' => true,
                'message' => 'Free delivery applied!',
            ];
        }

        // Calculate delivery cost
        $deliveryCost = $baseRate;
        $deliveryCost += $distance * $ratePerKm;
        $deliveryCost += $totalWeight * $ratePerKg;

        // Round to 2 decimal places
        $deliveryCost = round($deliveryCost, 2);

        return [
            'success' => true,
            'delivery_cost' => $deliveryCost,
            'distance_km' => $distance,
            'zone' => $zone->name,
            'estimated_days' => "{$zone->estimated_days_min}-{$zone->estimated_days_max}",
            'free_delivery' => false,
            'free_delivery_threshold' => $freeThreshold,
            'breakdown' => [
                'base_rate' => $baseRate,
                'distance_charge' => round($distance * $ratePerKm, 2),
                'weight_charge' => round($totalWeight * $ratePerKg, 2),
            ],
        ];
    }

    /**
     * Get supplier location coordinates
     */
    protected function getSupplierLocation(Supplier $supplier): array
    {
        // Try to find location by city
        if ($supplier->city) {
            $location = Location::findByCity($supplier->city, $supplier->province ?? null);
            if ($location && $location->latitude && $location->longitude) {
                return [
                    'latitude' => (float) $location->latitude,
                    'longitude' => (float) $location->longitude,
                ];
            }
        }

        // Try postal code
        if ($supplier->postal_code) {
            $location = Location::findByPostalCode($supplier->postal_code);
            if ($location && $location->latitude && $location->longitude) {
                return [
                    'latitude' => (float) $location->latitude,
                    'longitude' => (float) $location->longitude,
                ];
            }
        }

        // Default to Johannesburg (central SA)
        return [
            'latitude' => -26.2041,
            'longitude' => 28.0473,
        ];
    }

    /**
     * Get destination location coordinates
     */
    protected function getDestinationLocation(
        string $city,
        ?string $province = null,
        ?string $postalCode = null
    ): array {
        // Try postal code first
        if ($postalCode) {
            $location = Location::findByPostalCode($postalCode);
            if ($location && $location->latitude && $location->longitude) {
                return [
                    'latitude' => (float) $location->latitude,
                    'longitude' => (float) $location->longitude,
                ];
            }
        }

        // Try city
        $location = Location::findByCity($city, $province);
        if ($location && $location->latitude && $location->longitude) {
            return [
                'latitude' => (float) $location->latitude,
                'longitude' => (float) $location->longitude,
            ];
        }

        // Default to Johannesburg
        return [
            'latitude' => -26.2041,
            'longitude' => 28.0473,
        ];
    }

    /**
     * Calculate distance between two points using Haversine formula
     */
    public function calculateDistance(array $from, array $to): int
    {
        $earthRadius = 6371; // km

        $latFrom = deg2rad($from['latitude']);
        $lonFrom = deg2rad($from['longitude']);
        $latTo = deg2rad($to['latitude']);
        $lonTo = deg2rad($to['longitude']);

        $latDelta = $latTo - $latFrom;
        $lonDelta = $lonTo - $lonFrom;

        $a = sin($latDelta / 2) * sin($latDelta / 2) +
            cos($latFrom) * cos($latTo) *
            sin($lonDelta / 2) * sin($lonDelta / 2);

        $c = 2 * atan2(sqrt($a), sqrt(1 - $a));

        $distance = $earthRadius * $c;

        return (int) round($distance);
    }

    /**
     * Get all delivery zones with rates for a supplier
     */
    public function getSupplierDeliveryZones(Supplier $supplier): array
    {
        $zones = DeliveryZone::where('is_active', true)
            ->orderBy('min_distance_km')
            ->get();

        $result = [];

        foreach ($zones as $zone) {
            $supplierRate = SupplierDeliveryRate::where('supplier_id', $supplier->id)
                ->where('delivery_zone_id', $zone->id)
                ->first();

            $result[] = [
                'zone' => $zone,
                'supplier_rate' => $supplierRate,
                'effective_base_rate' => $supplierRate?->base_rate ?? $zone->base_rate,
                'effective_rate_per_km' => $supplierRate?->rate_per_km ?? $zone->rate_per_km,
                'effective_rate_per_kg' => $supplierRate?->rate_per_kg ?? $zone->rate_per_kg,
                'free_delivery_threshold' => $supplierRate?->free_delivery_threshold,
            ];
        }

        return $result;
    }

    /**
     * Estimate delivery cost for checkout preview
     */
    public function estimateDeliveryForCart(
        array $cartItems,
        string $destinationCity,
        ?string $destinationProvince = null,
        ?string $destinationPostalCode = null
    ): array {
        $deliveryBySupplier = [];
        $totalDeliveryCost = 0;

        // Group items by supplier
        $itemsBySupplier = [];
        foreach ($cartItems as $item) {
            $supplierId = $item['supplier_id'];
            if (!isset($itemsBySupplier[$supplierId])) {
                $itemsBySupplier[$supplierId] = [
                    'supplier' => $item['supplier'],
                    'items' => [],
                    'total' => 0,
                    'weight' => 0,
                ];
            }
            $itemsBySupplier[$supplierId]['items'][] = $item;
            $itemsBySupplier[$supplierId]['total'] += $item['total_price'];
            $itemsBySupplier[$supplierId]['weight'] += ($item['weight'] ?? 0) * $item['quantity'];
        }

        // Calculate delivery for each supplier
        foreach ($itemsBySupplier as $supplierId => $data) {
            $result = $this->calculateDeliveryCost(
                $data['supplier'],
                $destinationCity,
                $destinationProvince,
                $destinationPostalCode,
                $data['weight'],
                $data['total']
            );

            $deliveryBySupplier[$supplierId] = [
                'supplier_name' => $data['supplier']->company_name,
                'delivery' => $result,
            ];

            if ($result['success']) {
                $totalDeliveryCost += $result['delivery_cost'];
            }
        }

        return [
            'total_delivery_cost' => $totalDeliveryCost,
            'by_supplier' => $deliveryBySupplier,
        ];
    }
}
