<?php

declare(strict_types=1);

namespace Autogedal\AwbRetry\Model;

use Eadesigndev\Awb\Model\AwbRepository;
use Magento\Framework\Stdlib\DateTime\DateTime;
use Magento\Framework\Serialize\Serializer\Json;
class AwbErrorLogManager
{
    public function __construct(
        private readonly ErrorLogFactory $errorLogFactory,
        private readonly \Autogedal\AwbRetry\Model\ResourceModel\ErrorLog $errorLogResource,
        private readonly AwbRepository $awbRepository,
        private readonly Json $json,
        private readonly FailureEmailSender $failureEmailSender,
        private readonly DateTime $dateTime
    ) {
    }

    public function upsertFailure(
        int $awbId,
        int $carrierId,
        string $carrierCode,
        ?\Throwable $exception,
        int $attemptCount = 1,
        array $context = []
    ): ErrorLog {
        $attemptCount = max(1, $attemptCount);
        $awb = $this->awbRepository->getById($awbId);
        $existing = $this->findExisting($awbId, $carrierId);
        $log = $existing ?: $this->errorLogFactory->create();
        $emailSent = $existing ? (int) $existing->getData('email_sent') : 0;

        $carrierLabel = \Eadesigndev\Awb\Model\Sources\CarrierType::getAvailableStatic()[$carrierId] ?? (string) $carrierId;
        $log->addData([
            'awb_id' => $awbId,
            'order_id' => (int) $awb->getData('order_id') ?: null,
            'carrier_id' => $carrierId,
            'carrier_code' => $carrierCode,
            'carrier_label' => (string) $carrierLabel,
            'attempt_count' => $existing ? ((int) $existing->getData('attempt_count') + $attemptCount) : $attemptCount,
            'status' => 'failed',
            'email_sent' => $emailSent,
            'last_error_message' => $exception ? $exception->getMessage() : (string) __('Unknown error'),
            'last_exception_class' => $exception ? get_class($exception) : null,
            'request_context' => $this->json->serialize($context),
            'last_retry_at' => $this->getUtcDateTime(),
            'resolved_at' => null,
        ]);

        $this->errorLogResource->save($log);

        if ($emailSent === 0 && $this->failureEmailSender->send($log)) {
            $this->markEmailSent((int) $log->getId());
        }

        return $log;
    }

    public function resolveForAwb(int $awbId, int $carrierId, string $awbNumber): bool
    {
        $awbNumber = trim($awbNumber);
        if ($awbNumber === '') {
            return false;
        }

        $log = $this->findExisting($awbId, $carrierId);
        if (!$log || (string) $log->getData('status') === 'resolved') {
            return false;
        }

        $connection = $this->errorLogResource->getConnection();
        $connection->beginTransaction();
        try {
            $this->markGenerated((int) $log->getId(), $awbNumber);
            $this->markResolved((int) $log->getId());
            $connection->commit();
        } catch (\Throwable $throwable) {
            $connection->rollBack();
            throw $throwable;
        }

        return true;
    }

    public function markResolved(int $errorLogId): ErrorLog
    {
        $log = $this->loadOrFail($errorLogId);
        $log->setData('status', 'resolved');
        $log->setData('resolved_at', $this->getUtcDateTime());
        $log->setData('last_retry_at', $this->getUtcDateTime());
        $this->errorLogResource->save($log);

        return $log;
    }

    public function markEmailSent(int $errorLogId): ErrorLog
    {
        $log = $this->loadOrFail($errorLogId);
        $log->setData('email_sent', 1);
        $this->errorLogResource->save($log);

        return $log;
    }

    public function markQueued(int $errorLogId): ErrorLog
    {
        $log = $this->loadOrFail($errorLogId);
        $log->setData('status', 'queued');
        $log->setData('last_retry_at', $this->getUtcDateTime());
        $log->setData('resolved_at', null);
        $this->errorLogResource->save($log);

        return $log;
    }

    public function markProcessing(int $errorLogId): ErrorLog
    {
        $log = $this->loadOrFail($errorLogId);
        $log->setData('status', 'processing');
        $log->setData('last_retry_at', $this->getUtcDateTime());
        $log->setData('resolved_at', null);
        $this->errorLogResource->save($log);

        return $log;
    }

    public function markGenerated(int $errorLogId, string $awbNumber): ErrorLog
    {
        $log = $this->loadOrFail($errorLogId);
        $requestContext = [];
        $rawRequestContext = (string) $log->getData('request_context');
        if ($rawRequestContext !== '') {
            try {
                $decodedRequestContext = $this->json->unserialize($rawRequestContext);
                if (is_array($decodedRequestContext)) {
                    $requestContext = $decodedRequestContext;
                }
            } catch (\Throwable) {
                $requestContext = [];
            }
        }

        $requestContext['generated_awb_number'] = $awbNumber;
        $requestContext['generated_at'] = $this->getUtcDateTime();

        $log->setData('generated_awb_number', $awbNumber);
        $log->setData('generated_at', $this->getUtcDateTime());
        $log->setData('request_context', $this->json->serialize($requestContext));
        $this->errorLogResource->save($log);

        return $log;
    }

    public function markFailed(int $errorLogId, ?string $errorMessage = null): ErrorLog
    {
        $log = $this->loadOrFail($errorLogId);
        $log->setData('status', 'failed');
        $log->setData('last_retry_at', $this->getUtcDateTime());
        $log->setData('resolved_at', null);
        if ($errorMessage !== null && $errorMessage !== '') {
            $log->setData('last_error_message', $errorMessage);
        }
        $this->errorLogResource->save($log);

        return $log;
    }

    public function hasOrderErrors(int $orderId): bool
    {
        if ($orderId <= 0) {
            return false;
        }

        $connection = $this->errorLogResource->getConnection();
        $select = $connection->select()
            ->from($this->errorLogResource->getMainTable(), new \Zend_Db_Expr('COUNT(*)'))
            ->where('order_id = ?', $orderId);

        return (int) $connection->fetchOne($select) > 0;
    }

    public function loadById(int $errorLogId): ErrorLog
    {
        try {
            return $this->loadOrFail($errorLogId);
        } catch (\Magento\Framework\Exception\LocalizedException) {
            throw new \Magento\Framework\Exception\LocalizedException(__('The selected AWB error log no longer exists.'));
        }
    }

    private function findExisting(int $awbId, int $carrierId): ?ErrorLog
    {
        $connection = $this->errorLogResource->getConnection();
        $select = $connection->select()
            ->from($this->errorLogResource->getMainTable())
            ->where('awb_id = ?', $awbId)
            ->where('carrier_id = ?', $carrierId)
            ->order('entity_id DESC')
            ->limit(1);
        $row = $connection->fetchRow($select);
        if (!$row) {
            return null;
        }

        /** @var ErrorLog $log */
        $log = $this->errorLogFactory->create();
        $this->errorLogResource->load($log, (int) $row['entity_id']);
        return $log;
    }

    private function loadOrFail(int $errorLogId): ErrorLog
    {
        /** @var ErrorLog $log */
        $log = $this->errorLogFactory->create();
        $this->errorLogResource->load($log, $errorLogId);

        if (!$log->getId()) {
            throw new \Magento\Framework\Exception\LocalizedException(__('Unable to find the selected AWB error log.'));
        }

        return $log;
    }

    private function getUtcDateTime(): string
    {
        return $this->dateTime->gmtDate();
    }
}
