<?php

declare(strict_types=1);

namespace CloudXM\NFe\Services;

use CloudXM\NFe\Config\Configuration;
use CloudXM\NFe\DTOs\CancelInvoiceRequest;
use CloudXM\NFe\DTOs\InvoiceResource;
use CloudXM\NFe\Persistence\NfeEmissionsRepository;
use CloudXM\NFe\Services\ApiClient;
use CloudXM\NFe\Services\NFeFileStorageService;
use CloudXM\NFe\Services\NFeSchedulingService;
use CloudXM\NFe\Services\OrderMapperV2;
use CloudXM\SDK\Support\HasLogger;

/**
 * NFe Automation Service
 *
 * Handles auto-issuance, automatic downloads, grace period enforcement, and duplicate prevention
 * Updated for v2 API with DTOs
 */
class NFeAutomationService
{
    use HasLogger;

    public function __construct(
        private Configuration $config,
        private NfeEmissionsRepository $emissionRepo,
        private ApiClient $apiClient,
        private NFeFileStorageService $fileStorage,
        private OrderMapperV2 $orderMapper,
        private NFeSchedulingService $schedulingService
    ) {}

    /**
     * Check if auto-issue is enabled
     *
     * @return bool True if auto-issue is enabled
     */
    public function isAutoIssueEnabled(): bool
    {
        return $this->config->isAutoIssueEnabled();
    }

    // -------------------------------------------------------------------------
    // Auto-issue state machine (CLO-11)
    // -------------------------------------------------------------------------

    /**
     * Read the current auto_status from the order.
     */
    public function getAutoStatus(int $orderId): string
    {
        $order = wc_get_order($orderId);
        if (!$order) {
            return Configuration::AUTO_PENDING;
        }
        $value = $order->get_meta(Configuration::META_AUTO_STATUS);
        return is_string($value) && $value !== '' ? $value : Configuration::AUTO_PENDING;
    }

    /**
     * Update the auto_status on the order and persist.
     */
    public function setAutoStatus(int $orderId, string $status): void
    {
        $order = wc_get_order($orderId);
        if (!$order) {
            return;
        }
        $order->update_meta_data(Configuration::META_AUTO_STATUS, $status);
        $order->save();
    }

    /**
     * Resolve the schedule date from the existing _nfe_cloudxm_scheduled_at field.
     */
    private function getScheduleDate(int $orderId): ?string
    {
        $order = wc_get_order($orderId);
        if (!$order) {
            return null;
        }
        $field = $this->config->getScheduleDateField();
        $value = $order->get_meta($field);
        return is_string($value) && $value !== '' ? $value : null;
    }

    /**
     * Process the order based on its auto_status.
     *
     * State machine:
     *   ready_now + existing schedule in DB → cancel schedule + issue immediately (override)
     *   ready_now + schedule date field      → scheduleInvoice → status moves to schedule
     *   ready_now + no schedule date         → autoIssueNFe    → status moves to issued | error
     *
     * Returns null when no action was taken, or the result array otherwise.
     */
    public function processOrderFlags(int $orderId): ?array
    {
        $autoStatus = $this->getAutoStatus($orderId);
        if ($autoStatus !== Configuration::AUTO_READY_NOW) {
            return null;
        }

        $lifecycle = \CloudXM\NFe\Bootstrap\Plugin::getInstance()
            ->container()
            ->get(\CloudXM\NFe\Services\OrderInvoiceLifecycleService::class);

        // Override: order has a scheduled emission → cancel schedule and issue immediately
        $emissionRepo = \CloudXM\NFe\Bootstrap\Plugin::getInstance()
            ->container()
            ->get(\CloudXM\NFe\Persistence\NfeEmissionsRepository::class);
        $existing = $emissionRepo->findByOrderId($orderId, false);
        $hasScheduledEmission = $existing && in_array($existing['status'] ?? '', ['scheduled', 'async_pending'], true);

        if ($hasScheduledEmission) {
            $result = $lifecycle->issueInvoice($orderId);
            $this->setAutoStatus($orderId, ($result['success'] ?? false)
                ? Configuration::AUTO_ISSUED
                : Configuration::AUTO_ERROR);
            return $result;
        }

        $scheduleDate = $this->getScheduleDate($orderId);

        if ($scheduleDate !== null) {
            $result = $lifecycle->scheduleInvoice($orderId, $scheduleDate);
            $this->setAutoStatus($orderId, ($result['success'] ?? false)
                ? Configuration::AUTO_SCHEDULE
                : Configuration::AUTO_ERROR);
            return $result;
        }

        $result = $this->autoIssueNFe($orderId);
        $this->setAutoStatus($orderId, ($result['success'] ?? false)
            ? Configuration::AUTO_ISSUED
            : Configuration::AUTO_ERROR);
        return $result;
    }

    /**
     * Check if order is eligible for auto-issuance
     *
     * @param int $orderId Order ID
     * @return array Result with 'eligible' bool and 'reason' string
     */
    public function checkAutoIssueEligibility(int $orderId): array
    {
        // Check if auto-issue is enabled
        if (!$this->isAutoIssueEnabled()) {
            return [
                'eligible' => false,
                'reason' => 'Auto-issue is disabled'
            ];
        }

        // Check if order exists
        $order = wc_get_order($orderId);
        if (!$order) {
            return [
                'eligible' => false,
                'reason' => 'Order not found'
            ];
        }

        // Check if order is completed
        if (!$order->is_paid()) {
            return [
                'eligible' => false,
                'reason' => 'Order is not paid'
            ];
        }

        // Check if NFe already exists for this order
        $existing = $this->emissionRepo->findByOrderId($orderId);
        if ($existing) {
            return [
                'eligible' => false,
                'reason' => 'NFe already issued for this order',
                'emission_id' => $existing['id']
            ];
        }

        // Check if order has required data
        $billing = $order->get_address('billing');
        if (empty($billing['first_name']) || empty($billing['last_name'])) {
            return [
                'eligible' => false,
                'reason' => 'Missing billing information'
            ];
        }

        if (empty($billing['email'])) {
            return [
                'eligible' => false,
                'reason' => 'Missing customer email'
            ];
        }

        return [
            'eligible' => true,
            'reason' => 'Order is eligible for auto-issuance'
        ];
    }

    /**
     * Auto-issue NFe for order
     *
     * @param int $orderId Order ID
     * @return array Result with 'success' bool and 'message' string
     */
    public function autoIssueNFe(int $orderId): array
    {
        $this->log()->info('nfe.auto_issue.started', ['order_id' => $orderId]);

        // Check eligibility
        $eligibility = $this->checkAutoIssueEligibility($orderId);
        if (!$eligibility['eligible']) {
            $this->log()->info('nfe.auto_issue.not_eligible', [
                'order_id' => $orderId,
                'reason' => $eligibility['reason']
            ]);
            return [
                'success' => false,
                'message' => $eligibility['reason']
            ];
        }

        // Check for duplicate emission (prevent race conditions)
        $existing = $this->emissionRepo->findByOrderId($orderId);
        if ($existing) {
            $this->log()->warning('nfe.auto_issue.duplicate_prevented', [
                'order_id' => $orderId,
                'existing_emission_id' => $existing['id']
            ]);
            return [
                'success' => false,
                'message' => 'NFe already exists for this order',
                'emission_id' => $existing['id']
            ];
        }

        try {
            // Get WooCommerce order
            $order = wc_get_order($orderId);
            if (!$order) {
                throw new \Exception('Order not found');
            }

            // Map order to v2 request DTO using OrderMapperV2
            $request = $this->orderMapper->mapOrderToInvoiceRequest($order);

            // --- Scheduling decoration ---
            // When scheduled_at is populated → schedule for future emission
            // When scheduled_at is empty → issue immediately (no decoration)
            $scheduledAt = $this->schedulingService->resolveScheduledAt($order);

            if ($scheduledAt !== null) {
                $request = $this->schedulingService->buildScheduledRequest($order, $request);
            }

            // Call API to emit NFe using v2 method
            $invoiceResource = $this->apiClient->emitInvoiceV2($request);

            // Task 20.4 — async_pending is a valid emission status
            $emissionStatus = $invoiceResource->status ?? 'processing';

            // Handle synchronous failures — API returns 200 with status "failed" and error_message
            if ($emissionStatus === 'failed') {
                $errorMessage = $invoiceResource->error_message ?? __('Unknown error', 'woocommerce-cloudxm-nfe');

                $this->log()->warning('nfe.auto_issue.sync_failed', [
                    'order_id' => $orderId,
                    'invoice_id' => $invoiceResource->id,
                    'error' => $errorMessage,
                ]);

                $emissionData = [
                    'order_id'            => $orderId,
                    'invoice_id'          => $invoiceResource->id,
                    'status'              => 'failed',
                    'error_message'       => $errorMessage,
                    'processing_strategy' => $invoiceResource->processing_strategy,
                    'allowed_actions'     => json_encode($invoiceResource->allowed_actions),
                    'created_at'          => $invoiceResource->lifecycle?->created_at,
                    'total_value'         => (float) $order->get_total(),
                    'environment'         => $this->config->getEnvironment(),
                    'emission_date'       => null,
                ];

                $emissionId = $this->emissionRepo->create($emissionData);

                $this->setAutoStatus($orderId, Configuration::AUTO_ERROR);

                $order->update_meta_data('_cloudxm_nfe_status', 'failed');
                $order->update_meta_data('_cloudxm_nfe_emission_status', 'failed');
                $order->update_meta_data('_cloudxm_nfe_error', $errorMessage);
                $order->update_meta_data('_cloudxm_nfe_invoice_id', $invoiceResource->id);
                $order->save();

                $order->add_order_note(
                    sprintf(__('NFe falhou: %s', 'woocommerce-cloudxm-nfe'), $errorMessage),
                    0,
                    true
                );

                return [
                    'success' => false,
                    'message' => $errorMessage,
                    'emission_id' => $emissionId,
                    'invoice_id' => $invoiceResource->id,
                ];
            }

            // Save emission record with v2 fields
            $emissionData = [
                'order_id'            => $orderId,
                'invoice_id'          => $invoiceResource->id,
                'access_key'          => $invoiceResource->nfse?->access_key,
                'invoice_number'      => $invoiceResource->nfse?->number,
                'protocol_number'     => $invoiceResource->nfse?->protocol,
                'status'              => $emissionStatus,
                'processing_strategy' => $invoiceResource->processing_strategy,
                'allowed_actions'     => json_encode($invoiceResource->allowed_actions),
                'created_at'          => $invoiceResource->lifecycle?->created_at,
                'issued_at'           => $invoiceResource->lifecycle?->issued_at,
                'total_value'         => $invoiceResource->total_amount ?? $order->get_total(),
                'environment'         => $this->config->getEnvironment(),
                'emission_date'       => null,
            ];

            // Add scheduled fields if present
            if ($invoiceResource->lifecycle?->scheduled_for) {
                $emissionData['scheduled_for'] = $invoiceResource->lifecycle->scheduled_for;
            }
            if ($invoiceResource->lifecycle?->scheduled_for_local) {
                $emissionData['scheduled_for_local'] = $invoiceResource->lifecycle->scheduled_for_local;
            }
            if ($invoiceResource->lifecycle?->scheduled_timezone) {
                $emissionData['scheduled_timezone'] = $invoiceResource->lifecycle->scheduled_timezone;
            }

            $emissionId = $this->emissionRepo->create($emissionData);

            if (!$emissionId) {
                throw new \Exception('Failed to create emission record');
            }

            // Download and store files only for synchronous (immediate) emissions
            if ($emissionStatus !== 'async_pending' && $emissionStatus !== 'scheduled' && $invoiceResource->documents) {
                $this->autoDownloadFiles($emissionId, $invoiceResource);
            }

            // Order note varies by emission mode
            if ($emissionStatus === 'async_pending') {
                $note = __('NFe enviada para processamento assíncrono. Aguardando confirmação via webhook.', 'woocommerce-cloudxm-nfe');
            } elseif ($emissionStatus === 'scheduled') {
                $note = sprintf(
                    __('NFe agendada para emissão. Invoice ID: %s', 'woocommerce-cloudxm-nfe'),
                    $invoiceResource->id ?? 'N/A'
                );
            } else {
                $note = sprintf(
                    __('NFe emitted successfully. Access Key: %s, Invoice Number: %s, Processing: %s', 'woocommerce-cloudxm-nfe'),
                    $invoiceResource->nfse?->access_key ?? 'N/A',
                    $invoiceResource->nfse?->number ?? 'N/A',
                    $invoiceResource->processing_strategy ?? 'N/A'
                );
            }
            $order->add_order_note($note, 0, true);

            $this->setAutoStatus($orderId, Configuration::AUTO_ISSUED);

            $this->log()->info('nfe.auto_issue.completed', [
                'order_id' => $orderId,
                'emission_id' => $emissionId,
                'invoice_id' => $invoiceResource->id,
                'access_key' => $invoiceResource->nfse?->access_key,
                'processing_strategy' => $invoiceResource->processing_strategy,
                'status' => $invoiceResource->status
            ]);

            return [
                'success' => true,
                'message' => 'NFe emitted successfully',
                'emission_id' => $emissionId,
                'invoice_id' => $invoiceResource->id,
                'access_key' => $invoiceResource->nfse?->access_key
            ];

        } catch (\Exception $e) {
            $this->log()->error('nfe.auto_issue.exception', [
                'order_id' => $orderId,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            return [
                'success' => false,
                'message' => $e->getMessage()
            ];
        }
    }

    /**
     * Auto-download files for emission
     *
     * @param int $emissionId Emission ID
     * @param InvoiceResource $invoiceResource Invoice resource from v2 API
     */
    private function autoDownloadFiles(int $emissionId, InvoiceResource $invoiceResource): void
    {
        try {
            $emission = $this->emissionRepo->findById($emissionId);
            if (!$emission) {
                return;
            }

            $accessKey = $emission['access_key'];
            $emissionDate = new \DateTime($emission['emission_date']);

            // Download XML if URL is available
            if ($invoiceResource->documents?->xml_url) {
                $xmlContent = $this->apiClient->downloadXml($invoiceResource->id);
                if ($xmlContent) {
                    $this->fileStorage->storeXml($invoiceResource->id, $xmlContent, $accessKey, $emissionDate);
                    $this->log()->info('nfe.auto_download.xml_completed', [
                        'emission_id' => $emissionId,
                        'invoice_id' => $invoiceResource->id,
                        'access_key' => $accessKey
                    ]);
                }
            }

            // Download PDF if URL is available
            if ($invoiceResource->documents?->pdf_url) {
                $pdfContent = $this->apiClient->downloadPdf($invoiceResource->id);
                if ($pdfContent) {
                    $this->fileStorage->storePdf($invoiceResource->id, $pdfContent, $accessKey, $emissionDate);
                    $this->log()->info('nfe.auto_download.pdf_completed', [
                        'emission_id' => $emissionId,
                        'invoice_id' => $invoiceResource->id,
                        'access_key' => $accessKey
                    ]);
                }
            }

        } catch (\Exception $e) {
            $this->log()->warning('nfe.auto_download.failed', [
                'emission_id' => $emissionId,
                'invoice_id' => $invoiceResource->id,
                'error' => $e->getMessage()
            ]);
        }
    }

    /**
     * Check if cancellation is within grace period
     *
     * @param int $emissionId Emission ID
     * @return array Result with 'allowed' bool and 'reason' string
     */
    public function checkCancellationGracePeriod(int $emissionId): array
    {
        $emission = $this->emissionRepo->findById($emissionId);
        if (!$emission) {
            return [
                'allowed' => false,
                'reason' => 'Emission not found'
            ];
        }

        // Check if already cancelled
        if ($emission['status'] === 'cancelled') {
            return [
                'allowed' => false,
                'reason' => 'NFe already cancelled'
            ];
        }

        // Get grace period setting
        $graceDays = $this->config->getCancelDaysLimit();

        // Calculate grace period end date
        $emissionDate = new \DateTime($emission['emission_date']);
        $gracePeriodEnd = clone $emissionDate;
        $gracePeriodEnd->modify("+{$graceDays} days");

        $now = new \DateTime();

        // Check if within grace period
        if ($now <= $gracePeriodEnd) {
            return [
                'allowed' => true,
                'reason' => 'Within grace period',
                'grace_period_end' => $gracePeriodEnd->format('Y-m-d H:i:s')
            ];
        }

        return [
            'allowed' => false,
            'reason' => 'Grace period has expired',
            'grace_period_end' => $gracePeriodEnd->format('Y-m-d H:i:s')
        ];
    }

    /**
     * Cancel invoice using v2 API
     *
     * @param string $invoiceId Invoice ID
     * @param string $justification Cancellation justification
     * @return InvoiceResource Updated invoice resource
     * @throws \Exception if cancellation fails
     */
    public function cancelInvoice(string $invoiceId, string $justification): InvoiceResource
    {
        $this->log()->info('nfe.cancel.started', [
            'invoice_id' => $invoiceId,
            'justification_length' => strlen($justification)
        ]);

        try {
            // Create cancel request DTO
            $cancelRequest = new CancelInvoiceRequest($justification);

            // Call v2 API to cancel invoice
            $invoiceResource = $this->apiClient->cancelInvoiceV2($invoiceId, $cancelRequest);

            $this->log()->info('nfe.cancel.completed', [
                'invoice_id' => $invoiceId,
                'status' => $invoiceResource->status,
                'cancelled_at' => $invoiceResource->lifecycle?->cancelled_at,
                'processing_strategy' => $invoiceResource->processing_strategy
            ]);

            return $invoiceResource;

        } catch (\Exception $e) {
            $this->log()->error('nfe.cancel.exception', [
                'invoice_id' => $invoiceId,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            throw $e;
        }
    }

    /**
     * Get invoice status using v2 API
     *
     * @param string $invoiceId Invoice ID
     * @return InvoiceResource Invoice resource with current status
     * @throws \Exception if status check fails
     */
    public function getInvoiceStatus(string $invoiceId): InvoiceResource
    {
        $this->log()->info('nfe.status_check.started', [
            'invoice_id' => $invoiceId
        ]);

        try {
            // Call v2 API to get invoice
            $invoiceResource = $this->apiClient->getInvoiceV2($invoiceId);

            $this->log()->info('nfe.status_check.completed', [
                'invoice_id' => $invoiceId,
                'status' => $invoiceResource->status,
                'processing_strategy' => $invoiceResource->processing_strategy,
                'allowed_actions' => $invoiceResource->allowed_actions
            ]);

            return $invoiceResource;

        } catch (\Exception $e) {
            $this->log()->error('nfe.status_check.exception', [
                'invoice_id' => $invoiceId,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            throw $e;
        }
    }
}
