<?php

namespace PortalPeople\palletline\components;

use Yii;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\httpclient\Client;

class PalletlineClient extends Component
{
    public $apiUrl = 'https://pl570.tms.live/api/v1';

    public $username;

    public $password;

    public $networkAccount = '';

    public $autoConfirm = false;

    public $debug = false;

    public $timeout = 30;

    private $httpClient;

    public function init()
    {
        parent::init();

        if (empty($this->apiUrl)) {
            throw new InvalidConfigException('Palletline API URL must be configured.');
        }

        if (empty($this->username) || empty($this->password)) {
            throw new InvalidConfigException('Palletline username and password must be configured.');
        }

        $this->httpClient = new Client([
            'baseUrl' => rtrim($this->apiUrl, '/'),
        ]);
    }

    public function createConsignment(array $payload, bool $commit = false): array
    {
        try {
            $rows = $this->requestJson('POST', '/consignment', $this->injectNetworkAccount($payload));
            $wrapped = $this->wrapConsignmentResult($rows, 'Consignment created');

            if ($commit && $this->autoConfirm) {
                $consignmentId = (int) ($rows[0]['id'] ?? 0);
                if ($consignmentId > 0) {
                    $wrapped['Detail']['Transmit'] = $this->transmitConsignments([$consignmentId]);
                }
            }

            return $wrapped;
        } catch (\Exception $e) {
            return $this->handleError('/consignment', $e);
        }
    }

    public function getConStatusById(string $responseId): array
    {
        return $this->getConsignment($responseId);
    }

    public function getConStatusByConNo(string $conNo): array
    {
        throw new \RuntimeException('Lookup by consignment number is not supported by the Palletline API.');
    }

    public function getConStatusByTrackingId(string $trackingId): array
    {
        throw new \RuntimeException('Lookup by tracking ID is not supported by the Palletline API.');
    }

    public function getConsEntered(string $date = ''): array
    {
        return $this->requestJson('GET', '/consignments/' . rawurlencode($date ?: date('Y-m-d')));
    }

    public function getConsignment(string $trackingId): array
    {
        return $this->requestJson('GET', '/consignment/' . rawurlencode($trackingId));
    }

    public function manifestConsignment(string $responseId): array
    {
        return $this->transmitConsignments([(int) $responseId]);
    }

    public function deleteConsignment(string $responseId): array
    {
        throw new \RuntimeException('Deleting consignments is not exposed by the Palletline API.');
    }

    public function getTrackingNotesByConNo(string $conNo): array
    {
        throw new \RuntimeException('Lookup by consignment number is not supported by the Palletline API.');
    }

    public function getTrackingNotes(string $trackingId): array
    {
        return $this->requestJson('GET', '/consignment/' . rawurlencode($trackingId) . '/events');
    }

    public function getAvailableServices(
        string $consignmentType,
        string $collectionCountry,
        string $collectionPostcode,
        string $deliveryCountry,
        string $deliveryPostcode
    ): array {
        return $this->requestJson(
            'GET',
            '/availableServices/'
                . rawurlencode($consignmentType)
                . '/' . rawurlencode($collectionCountry)
                . '/' . rawurlencode($collectionPostcode)
                . '/' . rawurlencode($deliveryCountry)
                . '/' . rawurlencode($deliveryPostcode)
        );
    }

    public function getLabelsById(string $responseId): string
    {
        return $this->requestPdf('GET', '/consignment/' . rawurlencode($responseId) . '/label/a4');
    }

    public function getLabelsByConNo(string $conNo): string
    {
        throw new \RuntimeException('Lookup by consignment number is not supported by the Palletline API.');
    }

    public function getPodByTrackingId(string $trackingId): string
    {
        $files = $this->requestJson('GET', '/consignment/' . rawurlencode($trackingId) . '/pods');
        $base64 = (string) ($files[0]['base64EncodedFileContents'] ?? '');

        if ($base64 === '') {
            throw new \RuntimeException('No POD file returned by Palletline.');
        }

        $binary = base64_decode($base64, true);
        if ($binary === false) {
            throw new \RuntimeException('Invalid POD payload returned by Palletline.');
        }

        return $binary;
    }

    public function getThermalLabels(string $consignmentId, string $labelFormat = 'PDF'): array
    {
        return $this->requestJson(
            'GET',
            '/consignment/' . rawurlencode($consignmentId) . '/label/thermal',
            [],
            ['labelFormat' => strtoupper($labelFormat)]
        );
    }

    public function transmitConsignments(array $consignmentIds): array
    {
        $data = $this->requestJson('POST', '/consignments/transmit', [
            'consignmentIds' => array_values(array_map('intval', $consignmentIds)),
        ]);

        return [
            'Response' => [
                'Status' => [
                    'Code' => 'OK',
                    'Description' => 'Transmit request submitted',
                ],
            ],
            'data' => $data,
        ];
    }

    public function testConnection(): array
    {
        try {
            $result = $this->getAvailableServices('D', 'GB', 'CV3 2SF', 'GB', 'B37 7HB');

            if (!empty($result['collectionServices']) || !empty($result['deliveryServices'])) {
                return ['success' => true, 'message' => 'Connection to Palletline API successful.'];
            }

            return ['success' => false, 'message' => 'API reachable but did not return any services.'];
        } catch (\Exception $e) {
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }

    private function requestJson(string $method, string $path, array $payload = [], array $query = []): array
    {
        $url = $path;
        if (!empty($query)) {
            $url .= '?' . http_build_query($query);
        }

        $this->log($method . ' ' . $url, $payload ?: null);

        $request = $this->httpClient->createRequest()
            ->setMethod($method)
            ->setUrl($url)
            ->addHeaders(array_merge($this->authHeaders(), ['Content-Type' => 'application/json']))
            ->setFormat(Client::FORMAT_JSON)
            ->setOptions(['timeout' => $this->timeout]);

        if ($method !== 'GET') {
            $request->setData($payload);
        }

        $response = $request->send();
        if (!$response->getIsOk()) {
            $body = (string) $response->getContent();
            throw new \RuntimeException('HTTP ' . $response->getStatusCode() . ($body !== '' ? ': ' . $body : ''));
        }

        $data = json_decode((string) $response->getContent(), true) ?? [];
        $this->log('Response ' . $url, $data);

        return $data;
    }

    private function requestPdf(string $method, string $path): string
    {
        $response = $this->httpClient->createRequest()
            ->setMethod($method)
            ->setUrl($path)
            ->addHeaders([
                'Authorization' => 'Basic ' . base64_encode($this->username . ':' . $this->password),
                'Accept' => 'application/pdf',
            ])
            ->setOptions(['timeout' => $this->timeout])
            ->send();

        if (!$response->getIsOk()) {
            $body = (string) $response->getContent();
            throw new \RuntimeException('HTTP ' . $response->getStatusCode() . ($body !== '' ? ': ' . $body : ''));
        }

        return (string) $response->getContent();
    }

    private function authHeaders(): array
    {
        return [
            'Authorization' => 'Basic ' . base64_encode($this->username . ':' . $this->password),
            'Accept' => 'application/json',
        ];
    }

    private function injectNetworkAccount(array $payload): array
    {
        if ($this->networkAccount !== '' && empty($payload['networkAccount'])) {
            $payload['networkAccount'] = $this->networkAccount;
        }

        return $payload;
    }

    private function wrapConsignmentResult(array $rows, string $message): array
    {
        $first = $rows[0] ?? [];

        return [
            'Status' => [
                'Code' => 'OK',
                'Description' => $message,
            ],
            'Response' => [
                'Status' => [
                    'Code' => 'OK',
                    'Description' => $message,
                ],
            ],
            'Detail' => [
                'ImportDetail' => [[
                    'ResponseID' => (string) ($first['id'] ?? ''),
                    'Information' => (string) ($first['consignmentStatus'] ?? $message),
                ]],
            ],
            'data' => $rows,
        ];
    }

    private function handleError(string $path, \Exception $e): array
    {
        $msg = 'Palletline API error [' . $path . ']: ' . $e->getMessage();
        Yii::error($msg, 'palletline');

        return [
            'Response' => [
                'Status' => [
                    'Code' => 'ERROR',
                    'Description' => $e->getMessage(),
                ],
            ],
            'Status' => [
                'Code' => 'ERROR',
                'Description' => $e->getMessage(),
            ],
        ];
    }

    private function log(string $message, $context = null): void
    {
        if (!$this->debug) {
            return;
        }

        $msg = '[Palletline] ' . $message;
        if ($context !== null) {
            $msg .= ' | ' . json_encode($context, JSON_UNESCAPED_SLASHES);
        }

        Yii::info($msg, 'palletline');
    }
}
