<?php

namespace App\Services\Carriers;

use App\Exceptions\CarrierCreateFailedException;
use App\Models\Order;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class EkartCarrier implements CarrierInterface
{
    protected Client $client;
    protected array $config;
    protected ?string $token = null;

    public function __construct(array $config)
    {
        $this->config = $config;
        $this->client = new Client([
            'base_uri' => $config['api_url'] ?? 'https://app.elite.ekartlogistics.in',
            'timeout' => 30,
            'verify' => public_path('cacert.pem'),
        ]);
    }

    public function createShipment(Order $order, array $formData = []): array
    {
        try {
            $token = $this->getToken();
            $address = $order->address_snapshot;
            $codAmount = $formData['cod_amount'] ?? ($order->payment_method === 'cod' ? $order->total_payable : 0);
            $paymentMode = $formData['payment_mode'] ?? ($codAmount > 0 ? 'COD' : 'Prepaid');
            
            // Calculate tax values
            $taxValue = $formData['tax_value'] ?? ($order->tax_total ?? 0);
            $totalAmount = $order->total_payable;
            $taxableAmount = $totalAmount - $taxValue;
            $commodityValue = (string) $taxableAmount;

            // Build location objects
            $dropLocation = [
                'name' => $address['name'],
                'address' => $address['line1'] . ' ' . ($address['line2'] ?? ''),
                'city' => $address['city'] ?? '',
                'state' => $address['state'] ?? '',
                'country' => 'India',
                'phone' => (int) $address['mobile'],
                'pin' => (int) $address['pin'],
                'location_type' => $formData['address_type'] ?? 'Home',
            ];

            $pickupLocation = [
                'name' => $formData['name'] ?? config('services.ekart.pickup_name') ?? 'Seller',
                'address' => $formData['consigner_address'] ?? config('services.ekart.consigner_address') ?? '',
                'city' => $formData['consigner_city'] ?? config('services.ekart.consigner_city') ?? '',
                'state' => $formData['consigner_state'] ?? config('services.ekart.consigner_state') ?? '',
                'country' => 'India',
                'phone' => (int) ($formData['consigner_phone'] ?? config('services.ekart.consigner_phone') ?? 0),
                'pin' => (int) ($formData['consigner_pincode'] ?? config('services.ekart.consigner_pincode') ?? 0),
                'location_type' => 'Office',
            ];

            $data = [
                'order_number' => '#' . $order->id,
                'invoice_number' => $formData['invoice_number'] ?? ('INV-' . $order->id),
                'invoice_date' => $formData['invoice_date'] ?? ($order->placed_at ? $order->placed_at->format('Y-m-d') : now()->format('Y-m-d')),
                'consignee_name' => $address['name'],
                'consignee_gst_tin' => $formData['consignee_gst_tin'] ?? '',
                'consignee_gst_amount' => $formData['consignee_gst_amount'] ?? 0,
                'seller_name' => $formData['name'] ?? config('services.ekart.pickup_name') ?? 'Seller',
                'seller_address' => $formData['consigner_address'] ?? config('services.ekart.consigner_address') ?? '',
                'seller_gst_tin' => $formData['seller_gst_tin'] ?? ($this->config['seller_gst_tin'] ?? ''),
                'seller_gst_amount' => $formData['seller_gst_amount'] ?? 0,
                'payment_mode' => $paymentMode,
                'category_of_goods' => $formData['category_of_goods'] ?? 'General',
                'products_desc' => $this->getProductsDescription($order),
                'items' => $this->mapProducts($order),
                'hsn_code' => $formData['hsn_code'] ?? '',
                'total_amount' => (int) $totalAmount,
                'cod_amount' => $codAmount,
                'taxable_amount' => $taxableAmount,
                'tax_value' => (int) $taxValue,
                'commodity_value' => $commodityValue,
                'return_reason' => $formData['return_reason'] ?? ($paymentMode === 'Pickup' ? 'Return' : ''),
                'quantity' => $order->items->sum('qty'),
                'weight' => $formData['weight'] ?? 55,
                'length' => $formData['length'] ?? 10,
                'width' => $formData['width'] ?? 5,
                'height' => $formData['height'] ?? 3,
                'drop_location' => $dropLocation,
            ];

            // Add optional fields
            if ($pickupLocation) {
                $data['pickup_location'] = $pickupLocation;
                $data['return_location'] = $pickupLocation;
            }
            if (!empty($formData['templateName'])) {
                $data['templateName'] = $formData['templateName'];
            }

            // log the data being sent
            Log::info('Ekart create shipment data', [
                'order_id' => $order->id,
                'data' => $data,
            ]);

            $options = $this->withAuth($token, $data);
            $options['http_errors'] = false;

            $response = $this->client->put('/api/v1/package/create', $options);
            $statusCode = $response->getStatusCode();
            $rawBody = (string) $response->getBody();
            $result = json_decode($rawBody, true);

            if (!is_array($result)) {
                $result = [];
            }

            if ($statusCode >= 400) {
                $message = $this->extractEkartErrorMessage($result, $rawBody, $statusCode);
                $failureReason = $this->detectEkartFailureReason($message);

                Log::error('Ekart shipment creation failed', [
                    'order_id' => $order->id,
                    'http_status' => $statusCode,
                    'response' => $result,
                    'raw_body' => $rawBody,
                    'failure_reason' => $failureReason,
                ]);

                throw new CarrierCreateFailedException(
                    'Failed to create Ekart shipment: ' . $message,
                    0,
                    null,
                    $result,
                    $failureReason
                );
            }

            if (isset($result['status']) && $result['status'] && isset($result['tracking_id'])) {
                $trackingId = $result['tracking_id'];
                $labelUrl = $this->generateLabel($trackingId);

                Log::info('Ekart shipment created', [
                    'order_id' => $order->id,
                    'tracking_id' => $trackingId,
                ]);

                return [
                    'tracking_id' => $trackingId,
                    'label_url' => $labelUrl,
                    'raw_response' => $result,
                    'metadata' => [
                        'vendor' => $result['vendor'] ?? null,
                        'barcodes' => $result['barcodes'] ?? null,
                    ],
                ];
            }

            Log::error('Ekart shipment creation failed', [
                'order_id' => $order->id,
                'response' => $result,
            ]);

            $message = $this->extractEkartErrorMessage($result);
            $failureReason = $this->detectEkartFailureReason($message);

            throw new CarrierCreateFailedException(
                'Failed to create Ekart shipment: ' . $message,
                0,
                null,
                $result,
                $failureReason
            );
        } catch (CarrierCreateFailedException $e) {
            throw $e;
        } catch (RequestException $e) {
            $result = [];
            $rawBody = '';
            $statusCode = null;
            if ($e->hasResponse()) {
                $statusCode = $e->getResponse()->getStatusCode();
                $rawBody = (string) $e->getResponse()->getBody();
                $decoded = json_decode($rawBody, true);
                if (is_array($decoded)) {
                    $result = $decoded;
                }
            }

            $message = $this->extractEkartErrorMessage($result, $rawBody, $statusCode, $e->getMessage());
            $failureReason = $this->detectEkartFailureReason($message);

            Log::error('Ekart shipment creation exception', [
                'order_id' => $order->id,
                'http_status' => $statusCode,
                'error' => $message,
                'response' => $result,
            ]);

            throw new CarrierCreateFailedException(
                'Failed to create Ekart shipment: ' . $message,
                0,
                $e,
                $result,
                $failureReason
            );
        } catch (\Exception $e) {
            Log::error('Ekart shipment creation exception', [
                'order_id' => $order->id,
                'error' => $e->getMessage(),
            ]);

            throw $e;
        }
    }

    public function generateLabel(string $trackingId): ?string
    {
        try {
            $token = $this->getToken();
            $response = $this->client->post('/api/v1/package/label', $this->withAuth($token, [
                'ids' => [$trackingId],
            ]));

            // The API returns a PDF binary, so we return the URL to download it
            $apiUrl = $this->config['api_url'] ?? 'https://app.elite.ekartlogistics.in';
            $labelUrl = $apiUrl . '/api/v1/package/label?json_only=false&ids[]=' . $trackingId;

            Log::info('Ekart label generated', [
                'tracking_id' => $trackingId,
                'url' => $labelUrl,
            ]);

            return $labelUrl;
        } catch (\Exception $e) {
            Log::error('Ekart label generation failed', [
                'tracking_id' => $trackingId,
                'error' => $e->getMessage(),
            ]);

            return null;
        }
    }

    public function track(string $trackingId): ?array
    {
        try {
            // Track endpoint is public (no auth required per spec.yaml)
            $response = $this->client->get('/api/v1/track/' . $trackingId);

            $result = json_decode($response->getBody()->getContents(), true);

            return $result['track'] ?? $result ?? null;
        } catch (\Exception $e) {
            Log::error('Ekart tracking failed', [
                'tracking_id' => $trackingId,
                'error' => $e->getMessage(),
            ]);

            return null;
        }
    }

    public function validateConfig(array $config): bool
    {
        return !empty($config['username']) && !empty($config['password']) && !empty($config['client_id']);
    }

    /**
     * Check delivery availability for a given pincode
     *
     * @param string $pincode
     * @return array Returns array with 'available' (bool) and 'message' (string)
     */
    public function checkDeliveryAvailability(string $pincode): array
    {
        try {
            $token = $this->getToken();
            // Ekart serviceability API endpoint - requires authentication
            $authOptions = $this->withAuth($token);
            // For GET request, only use headers (remove json key if present)
            $response = $this->client->get('/api/v2/serviceability/' . $pincode, [
                'headers' => $authOptions['headers'],
                'timeout' => 10,
            ]);

            $result = json_decode($response->getBody()->getContents(), true);
            
            // Check if status field exists and is true
            if (isset($result['status']) && $result['status'] === true) {
                return [
                    'available' => true,
                    'message' => 'Delivery available for this pincode',
                ];
            }

            $remark = $result['remark'] ?? 'Delivery not available for this pincode';
            return [
                'available' => false,
                'message' => $remark,
            ];
        } catch (\Exception $e) {
            Log::error('Ekart delivery availability check failed', [
                'pincode' => $pincode,
                'error' => $e->getMessage(),
            ]);

            // On error, assume not available to be safe
            return [
                'available' => false,
                'message' => 'Unable to verify delivery availability. Please try again later.',
            ];
        }
    }

    protected function getProductsDescription(Order $order): string
    {
        $items = $order->items->map(function ($item) {
            return $item->name . ' (x' . $item->qty . ')';
        })->take(3)->toArray();

        return implode(', ', $items);
    }

    protected function mapProducts(Order $order): array
    {
        return $order->items->map(function ($item) {
            return [
                'product_name' => $item->name,
                'quantity' => (int) $item->qty,
                'price' => (int) $item->line_total,
                'sku' => $item->sku ?? 'N/A',
            ];
        })->values()->toArray();
    }

    protected function getToken(): string
    {
        if ($this->token) {
            return $this->token;
        }

        $cacheKey = 'ekart_token_' . md5(($this->config['username'] ?? '') . '_' . ($this->config['client_id'] ?? ''));

        // Try to get from cache first
        $cached = Cache::get($cacheKey);
        if ($cached) {
            return $this->token = $cached;
        }

        if (empty($this->config['username']) || empty($this->config['password']) || empty($this->config['client_id'])) {
            throw new \RuntimeException('Ekart credentials missing');
        }

        $response = $this->client->post('/integrations/v2/auth/token/' . $this->config['client_id'], [
            'headers' => [
                'Content-Type' => 'application/json',
            ],
            'json' => [
                'username' => $this->config['username'],
                'password' => $this->config['password'],
            ],
        ]);

        $body = json_decode($response->getBody()->getContents(), true);
        $token = $body['access_token'] ?? null;
        $expiresIn = $body['expires_in'] ?? 86400;

        if (!$token) {
            throw new \RuntimeException('Ekart authentication failed: ' . ($body['message'] ?? 'Unknown error'));
        }

        // Cache for expires_in - 1 hour (safety margin)
        $cacheDuration = max(3600, $expiresIn - 3600);
        Cache::put($cacheKey, $token, now()->addSeconds($cacheDuration));

        return $this->token = $token;
    }

    protected function withAuth(string $token, array $json = []): array
    {
        return [
            'headers' => [
                'Authorization' => 'Bearer ' . $token,
                'Content-Type' => 'application/json',
            ],
            'json' => $json,
        ];
    }

    protected function extractEkartErrorMessage(
        array $result,
        string $rawBody = '',
        ?int $statusCode = null,
        string $fallback = 'Unknown error'
    ): string {
        $parts = [];

        foreach (['description', 'remark', 'message', 'error', 'code'] as $key) {
            $value = $result[$key] ?? null;
            if (is_string($value) && trim($value) !== '') {
                $parts[] = trim($value);
            }
        }

        $errors = $result['errors'] ?? null;
        if (is_array($errors)) {
            foreach ($errors as $error) {
                if (is_string($error) && trim($error) !== '') {
                    $parts[] = trim($error);
                } elseif (is_array($error)) {
                    foreach ($error as $nested) {
                        if (is_string($nested) && trim($nested) !== '') {
                            $parts[] = trim($nested);
                        }
                    }
                }
            }
        }

        if (empty($parts) && $rawBody !== '') {
            $parts[] = trim($rawBody);
        }

        $message = implode(' | ', array_values(array_unique(array_filter($parts))));
        if ($message === '') {
            $message = $fallback;
        }

        if ($statusCode !== null && !str_contains($message, 'HTTP ')) {
            $message = 'HTTP ' . $statusCode . ': ' . $message;
        }

        return $message;
    }

    protected function detectEkartFailureReason(string $message): string
    {
        $lower = strtolower($message);

        if (str_contains($lower, 'insufficient balance')) {
            return CarrierCreateFailedException::FAILURE_INSUFFICIENT_BALANCE;
        }

        if (str_contains($lower, 'non serviceable pincode')
            || str_contains($lower, 'non-serviceable pincode')
            || str_contains($lower, 'drop pincode')
            || str_contains($lower, 'not serviceable')
        ) {
            return CarrierCreateFailedException::FAILURE_NON_SERVICEABLE_PINCODE;
        }

        return CarrierCreateFailedException::FAILURE_UNKNOWN;
    }
}
