<?php

declare(strict_types=1);

namespace CloudXM\NFe\Services;

use CloudXM\NFe\Persistence\NfeHistoryRepository;
use CloudXM\SDK\Support\HasLogger;

/**
 * NFe History Service
 *
 * Handles event logging orchestration and history management
 */
class NFeHistoryService
{
    use HasLogger;

    /**
     * Event types
     */
    public const EVENT_TYPE_EMISSION = 'emission';
    public const EVENT_TYPE_CANCELLATION = 'cancellation';
    public const EVENT_TYPE_DOWNLOAD = 'download';
    public const EVENT_TYPE_STATUS_CHANGE = 'status_change';
    public const EVENT_TYPE_ERROR = 'error';

    /**
     * Event statuses
     */
    public const EVENT_STATUS_SUCCESS = 'success';
    public const EVENT_STATUS_FAILED = 'failed';
    public const EVENT_STATUS_PENDING = 'pending';

    public function __construct(
        private NfeHistoryRepository $historyRepo
    ) {}

    /**
     * Log emission event
     *
     * @param int $emissionId Emission ID
     * @param string $eventType Event type
     * @param array $data Event data
     * @param int|null $userId User ID who triggered the event
     * @return int|false History ID or false on failure
     */
    public function logEvent(int $emissionId, string $eventType, array $data = [], ?int $userId = null): int|false
    {
        $historyData = [
            'emission_id' => $emissionId,
            'event_type' => $eventType,
            'event_status' => self::EVENT_STATUS_SUCCESS,
            'event_data' => json_encode($data),
            'user_id' => $userId ?? get_current_user_id(),
            'ip_address' => $this->getClientIp(),
            'user_agent' => $this->getUserAgent(),
            'created_at' => current_time('mysql')
        ];

        $historyId = $this->historyRepo->add($historyData);

        if ($historyId) {
            // Include v2 fields in log context if present
            $logContext = [
                'history_id' => $historyId,
                'emission_id' => $emissionId,
                'event_type' => $eventType,
                'user_id' => $userId
            ];

            // Add v2-specific fields to log context if present in event data
            if (isset($data['processing_strategy'])) {
                $logContext['processing_strategy'] = $data['processing_strategy'];
            }
            if (isset($data['allowed_actions'])) {
                $logContext['allowed_actions'] = $data['allowed_actions'];
            }
            if (isset($data['lifecycle'])) {
                $logContext['lifecycle'] = $data['lifecycle'];
            }

            $this->log()->info('nfe.history.event_logged', $logContext);
        }

        return $historyId;
    }

    /**
     * Log error event
     *
     * @param int $emissionId Emission ID
     * @param string $errorType Error type
     * @param string $errorMessage Error message
     * @param array $context Additional context
     * @return int|false History ID or false on failure
     */
    public function logError(int $emissionId, string $errorType, string $errorMessage, array $context = []): int|false
    {
        $historyData = [
            'emission_id' => $emissionId,
            'event_type' => self::EVENT_TYPE_ERROR,
            'event_status' => self::EVENT_STATUS_FAILED,
            'event_data' => json_encode([
                'error_type' => $errorType,
                'error_message' => $errorMessage,
                'context' => $context
            ]),
            'user_id' => get_current_user_id(),
            'ip_address' => $this->getClientIp(),
            'user_agent' => $this->getUserAgent(),
            'created_at' => current_time('mysql')
        ];

        $historyId = $this->historyRepo->add($historyData);

        if ($historyId) {
            $this->log()->error('nfe.history.error_logged', [
                'history_id' => $historyId,
                'emission_id' => $emissionId,
                'error_type' => $errorType,
                'error_message' => $errorMessage
            ]);
        }

        return $historyId;
    }

    /**
     * Log invoice event with v2 data
     * 
     * Helper method to log invoice-related events with v2 fields
     *
     * @param int $emissionId Emission ID
     * @param string $eventType Event type
     * @param \CloudXM\NFe\DTOs\InvoiceResource $invoice Invoice resource with v2 data
     * @param array $additionalData Additional event data
     * @param int|null $userId User ID who triggered the event
     * @return int|false History ID or false on failure
     */
    public function logInvoiceEvent(
        int $emissionId,
        string $eventType,
        \CloudXM\NFe\DTOs\InvoiceResource $invoice,
        array $additionalData = [],
        ?int $userId = null
    ): int|false {
        // Build event data with v2 fields
        $eventData = array_merge($additionalData, [
            'invoice_id' => $invoice->id,
            'status' => $invoice->status,
            'processing_strategy' => $invoice->processing_strategy,
            'allowed_actions' => $invoice->allowed_actions,
        ]);

        // Add lifecycle timestamps if available
        if ($invoice->lifecycle !== null) {
            $eventData['lifecycle'] = [
                'created_at' => $invoice->lifecycle->created_at,
                'issued_at' => $invoice->lifecycle->issued_at,
                'cancelled_at' => $invoice->lifecycle->cancelled_at,
            ];
        }

        // Add NFSe data if available
        if ($invoice->nfse !== null) {
            $eventData['nfse'] = [
                'number' => $invoice->nfse->number,
                'series' => $invoice->nfse->series,
                'access_key' => $invoice->nfse->access_key,
            ];
        }

        return $this->logEvent($emissionId, $eventType, $eventData, $userId);
    }

    /**
     * Log status change with v2 lifecycle data
     *
     * @param int $emissionId Emission ID
     * @param string $oldStatus Previous status
     * @param string $newStatus New status
     * @param \CloudXM\NFe\DTOs\InvoiceResource $invoice Invoice resource with v2 data
     * @param int|null $userId User ID who triggered the event
     * @return int|false History ID or false on failure
     */
    public function logStatusChange(
        int $emissionId,
        string $oldStatus,
        string $newStatus,
        \CloudXM\NFe\DTOs\InvoiceResource $invoice,
        ?int $userId = null
    ): int|false {
        $eventData = [
            'old_status' => $oldStatus,
            'new_status' => $newStatus,
            'invoice_id' => $invoice->id,
            'processing_strategy' => $invoice->processing_strategy,
            'allowed_actions' => $invoice->allowed_actions,
        ];

        // Add lifecycle timestamps
        if ($invoice->lifecycle !== null) {
            $eventData['lifecycle'] = [
                'created_at' => $invoice->lifecycle->created_at,
                'issued_at' => $invoice->lifecycle->issued_at,
                'cancelled_at' => $invoice->lifecycle->cancelled_at,
            ];
        }

        return $this->logEvent($emissionId, self::EVENT_TYPE_STATUS_CHANGE, $eventData, $userId);
    }

    /**
     * Get history for emission
     *
     * @param int $emissionId Emission ID
     * @return array List of history events
     */
    public function getEmissionHistory(int $emissionId): array
    {
        return $this->historyRepo->findByEmissionId($emissionId);
    }

    /**
     * Get history by event type
     *
     * @param string $eventType Event type
     * @param int $limit Maximum number of records
     * @param int $offset Offset for pagination
     * @return array List of history events
     */
    public function getHistoryByType(string $eventType, int $limit = 50, int $offset = 0): array
    {
        return $this->historyRepo->findByEventType($eventType, $limit, $offset);
    }

    /**
     * Get history by event status
     *
     * @param string $eventStatus Event status
     * @param int $limit Maximum number of records
     * @param int $offset Offset for pagination
     * @return array List of history events
     */
    public function getHistoryByStatus(string $eventStatus, int $limit = 50, int $offset = 0): array
    {
        return $this->historyRepo->findByEventStatus($eventStatus, $limit, $offset);
    }

    /**
     * Get history by date range
     *
     * @param string $startDate Start date (Y-m-d H:i:s)
     * @param string $endDate End date (Y-m-d H:i:s)
     * @param int $limit Maximum number of records
     * @return array List of history events
     */
    public function getHistoryByDateRange(string $startDate, string $endDate, int $limit = 100): array
    {
        return $this->historyRepo->findByDateRange($startDate, $endDate, $limit);
    }

    /**
     * Get history by user
     *
     * @param int $userId User ID
     * @param int $limit Maximum number of records
     * @param int $offset Offset for pagination
     * @return array List of history events
     */
    public function getHistoryByUser(int $userId, int $limit = 50, int $offset = 0): array
    {
        return $this->historyRepo->findByUserId($userId, $limit, $offset);
    }

    /**
     * Get recent history events
     *
     * @param int $limit Maximum number of records
     * @return array List of recent events
     */
    public function getRecentHistory(int $limit = 20): array
    {
        return $this->historyRepo->getRecent($limit);
    }

    /**
     * Get history statistics for emission
     *
     * @param int $emissionId Emission ID
     * @return array Statistics data
     */
    public function getEmissionStatistics(int $emissionId): array
    {
        return $this->historyRepo->getStatisticsByEmission($emissionId);
    }

    /**
     * Delete history for emission
     *
     * @param int $emissionId Emission ID
     * @return int Number of records deleted
     */
    public function deleteEmissionHistory(int $emissionId): int
    {
        $deleted = $this->historyRepo->deleteByEmissionId($emissionId);

        if ($deleted > 0) {
            $this->log()->info('nfe.history.emission_deleted', [
                'emission_id' => $emissionId,
                'deleted_count' => $deleted
            ]);
        }

        return $deleted;
    }

    /**
     * Delete old history events
     *
     * @param int $daysOld Delete events older than this many days
     * @return int Number of records deleted
     */
    public function cleanupOldHistory(int $daysOld = 90): int
    {
        $cutoffDate = date('Y-m-d H:i:s', strtotime("-{$daysOld} days"));
        $deleted = $this->historyRepo->deleteOldEvents($cutoffDate);

        if ($deleted > 0) {
            $this->log()->info('nfe.history.cleanup_completed', [
                'deleted_count' => $deleted,
                'cutoff_date' => $cutoffDate
            ]);
        }

        return $deleted;
    }

    /**
     * Get client IP address
     *
     * @return string IP address
     */
    private function getClientIp(): string
    {
        $ip = '';

        if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
            $ip = $_SERVER['HTTP_CLIENT_IP'];
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED'])) {
            $ip = $_SERVER['HTTP_X_FORWARDED'];
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
            $ip = $_SERVER['HTTP_X_FORWARDED_HOST'];
        } elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) {
            $ip = $_SERVER['HTTP_X_REAL_IP'];
        } elseif (!empty($_SERVER['HTTP_X_CLUSTER_CLIENT_IP'])) {
            $ip = $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
        } elseif (!empty($_SERVER['HTTP_FORWARDED_FOR'])) {
            $ip = $_SERVER['HTTP_FORWARDED_FOR'];
        } elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) {
            $ip = $_SERVER['HTTP_CLIENT_IP'];
        } else {
            $ip = $_SERVER['REMOTE_ADDR'] ?? '';
        }

        return sanitize_text_field($ip);
    }

    /**
     * Get user agent
     *
     * @return string User agent
     */
    private function getUserAgent(): string
    {
        return sanitize_text_field($_SERVER['HTTP_USER_AGENT'] ?? '');
    }

    /**
     * Validate event type
     *
     * @param string $eventType Event type to validate
     * @return bool True if valid
     */
    public function isValidEventType(string $eventType): bool
    {
        $validTypes = [
            self::EVENT_TYPE_EMISSION,
            self::EVENT_TYPE_CANCELLATION,
            self::EVENT_TYPE_DOWNLOAD,
            self::EVENT_TYPE_STATUS_CHANGE,
            self::EVENT_TYPE_ERROR
        ];

        return in_array($eventType, $validTypes);
    }

    /**
     * Validate event status
     *
     * @param string $eventStatus Event status to validate
     * @return bool True if valid
     */
    public function isValidEventStatus(string $eventStatus): bool
    {
        $validStatuses = [
            self::EVENT_STATUS_SUCCESS,
            self::EVENT_STATUS_FAILED,
            self::EVENT_STATUS_PENDING
        ];

        return in_array($eventStatus, $validStatuses);
    }
}
