<?php

declare(strict_types=1);

namespace CloudXM\NFe\Services;

use CloudXM\NFe\Config\Configuration;
use CloudXM\SDK\Support\HasLogger;

/**
 * NFe File Storage Service
 *
 * Handles storage and retrieval of NFe PDF and XML files
 * Automatically downloads and stores files after issuance
 */
class NFeFileStorageService
{
    use HasLogger;

    /**
     * Configuration instance
     */
    private Configuration $config;

    /**
     * API Client instance
     */
    private ApiClient $apiClient;

    /**
     * Base upload directory
     */
    private string $uploadDir;

    /**
     * NFe files subdirectory
     */
    private string $nfeDir;

    /**
     * Constructor
     */
    public function __construct(Configuration $config, ApiClient $apiClient)
    {
        $this->config = $config;
        $this->apiClient = $apiClient;
        $this->uploadDir = wp_upload_dir()['basedir'];
        $this->nfeDir = $this->uploadDir . '/nfe-files';
        
        // Ensure directories exist
        $this->ensureSecureDirectory();
    }

    /**
     * Store NFe XML file
     *
     * @param string $invoiceId Invoice ID from NFE server
     * @param string $xmlContent XML content
     * @param string $accessKey NFe access key (chave de acesso)
     * @param \DateTime $emissionDate Emission date
     * @return string Relative path to stored file
     */
    public function storeXml(string $invoiceId, string $xmlContent, string $accessKey, \DateTime $emissionDate): string
    {
        $relativePath = $this->getFilePath($accessKey, 'xml', $emissionDate);
        $fullPath = $this->uploadDir . '/' . $relativePath;
        
        // Ensure directory exists
        $this->ensureDirectoryExists(dirname($fullPath));
        
        // Store file
        $result = file_put_contents($fullPath, $xmlContent);
        
        if ($result === false) {
            $this->log()->error('nfe.xml_storage_failed', [
                'invoice_id' => $invoiceId,
                'access_key' => $accessKey,
                'path' => $fullPath,
            ]);
            
            throw new \RuntimeException('Failed to store NFe XML file');
        }
        
        $this->log()->info('nfe.xml_stored', [
            'invoice_id' => $invoiceId,
            'access_key' => $accessKey,
            'path' => $relativePath,
        ]);
        
        return $relativePath;
    }

    /**
     * Store NFe PDF file
     *
     * @param string $invoiceId Invoice ID from NFE server
     * @param string $pdfContent PDF content
     * @param string $accessKey NFe access key (chave de acesso)
     * @param \DateTime $emissionDate Emission date
     * @return string Relative path to stored file
     */
    public function storePdf(string $invoiceId, string $pdfContent, string $accessKey, \DateTime $emissionDate): string
    {
        $relativePath = $this->getFilePath($accessKey, 'pdf', $emissionDate);
        $fullPath = $this->uploadDir . '/' . $relativePath;
        
        // Ensure directory exists
        $this->ensureDirectoryExists(dirname($fullPath));
        
        // Store file
        $result = file_put_contents($fullPath, $pdfContent);
        
        if ($result === false) {
            $this->log()->error('nfe.pdf_storage_failed', [
                'invoice_id' => $invoiceId,
                'access_key' => $accessKey,
                'path' => $fullPath,
            ]);
            
            throw new \RuntimeException('Failed to store NFe PDF file');
        }
        
        $this->log()->info('nfe.pdf_stored', [
            'invoice_id' => $invoiceId,
            'access_key' => $accessKey,
            'path' => $relativePath,
        ]);
        
        return $relativePath;
    }

    /**
     * Download and store NFe XML from API
     *
     * @param string $invoiceId Invoice ID from NFE server
     * @param string $accessKey NFe access key (chave de acesso)
     * @param \DateTime $emissionDate Emission date
     * @return string Relative path to stored file
     */
    public function downloadAndStoreXml(string $invoiceId, string $accessKey, \DateTime $emissionDate): string
    {
        // Download XML from API v2 (returns content directly)
        $xmlContent = $this->apiClient->downloadXmlV2($invoiceId);
        
        if (empty($xmlContent)) {
            throw new \RuntimeException('Failed to get XML from NFE server');
        }
        
        // Store file
        return $this->storeXml($invoiceId, $xmlContent, $accessKey, $emissionDate);
    }

    /**
     * Download and store NFe PDF from API
     *
     * @param string $invoiceId Invoice ID from NFE server
     * @param string $accessKey NFe access key (chave de acesso)
     * @param \DateTime $emissionDate Emission date
     * @return string Relative path to stored file
     */
    public function downloadAndStorePdf(string $invoiceId, string $accessKey, \DateTime $emissionDate): string
    {
        // Download PDF from API v2 (returns content directly)
        $pdfContent = $this->apiClient->downloadPdfV2($invoiceId);
        
        if (empty($pdfContent)) {
            throw new \RuntimeException('Failed to get PDF from NFE server');
        }
        
        // Store file
        return $this->storePdf($invoiceId, $pdfContent, $accessKey, $emissionDate);
    }

    /**
     * Delete cached PDF and XML files for a given access key and date.
     *
     * Used to invalidate stale files (e.g. before re-downloading after cancellation).
     *
     * @param string $accessKey NFe access key (chave de acesso)
     * @param \DateTime $emissionDate Emission date used when the files were stored
     * @return void
     */
    public function deleteCachedFiles(string $accessKey, \DateTime $emissionDate): void
    {
        foreach (['pdf', 'xml'] as $ext) {
            $relativePath = $this->getFilePath($accessKey, $ext, $emissionDate);
            if ($this->fileExists($relativePath)) {
                $this->deleteFile($relativePath);
                $this->log()->info('nfe.cache_invalidated', [
                    'access_key' => $accessKey,
                    'extension' => $ext,
                    'path' => $relativePath,
                ]);
            }
        }
    }

    /**
     * Download and store both PDF and XML files
     *
     * Note: Download failures are logged as warnings since the API client
     * already handles retries. Files can be downloaded manually later.
     *
     * @param string $invoiceId Invoice ID from NFE server
     * @param string $accessKey NFe access key (chave de acesso)
     * @param \DateTime $emissionDate Emission date
     * @return array Paths to stored files
     */
    public function downloadAndStoreAll(string $invoiceId, string $accessKey, \DateTime $emissionDate): array
    {
        $paths = [];

        try {
            // Download XML
            $paths['xml'] = $this->downloadAndStoreXml($invoiceId, $accessKey, $emissionDate);
        } catch (\Exception $e) {
            $this->log()->warning('nfe.xml_download_failed', [
                'invoice_id' => $invoiceId,
                'access_key' => $accessKey,
                'error' => $e->getMessage(),
                'note' => 'Files can be downloaded later via the UI',
            ]);
        }

        try {
            // Download PDF
            $paths['pdf'] = $this->downloadAndStorePdf($invoiceId, $accessKey, $emissionDate);
        } catch (\Exception $e) {
            $this->log()->warning('nfe.pdf_download_failed', [
                'invoice_id' => $invoiceId,
                'access_key' => $accessKey,
                'error' => $e->getMessage(),
                'note' => 'Files can be downloaded later via the UI',
            ]);
        }

        return $paths;
    }

    /**
     * Get file contents
     *
     * @param string $relativePath Relative path to file
     * @return string|null File contents or null if not found
     */
    public function getFileContents(string $relativePath): ?string
    {
        $fullPath = $this->uploadDir . '/' . $relativePath;
        
        if (!file_exists($fullPath)) {
            return null;
        }
        
        return file_get_contents($fullPath);
    }

    /**
     * Check if file exists
     *
     * @param string $relativePath Relative path to file
     * @return bool
     */
    public function fileExists(string $relativePath): bool
    {
        $fullPath = $this->uploadDir . '/' . $relativePath;
        return file_exists($fullPath);
    }

    /**
     * Get file size in bytes
     *
     * @param string $relativePath Relative path to file
     * @return int|null File size or null if not found
     */
    public function getFileSize(string $relativePath): ?int
    {
        $fullPath = $this->uploadDir . '/' . $relativePath;
        
        if (!file_exists($fullPath)) {
            return null;
        }
        
        return filesize($fullPath);
    }

    /**
     * Delete file
     *
     * @param string $relativePath Relative path to file
     * @return bool True if deleted, false if not found or failed
     */
    public function deleteFile(string $relativePath): bool
    {
        $fullPath = $this->uploadDir . '/' . $relativePath;
        
        if (!file_exists($fullPath)) {
            return false;
        }
        
        $result = unlink($fullPath);
        
        if ($result) {
            $this->log()->info('nfe.file_deleted', ['path' => $relativePath]);
        }
        
        return $result;
    }

    /**
     * Get file URL for download
     *
     * @param string $relativePath Relative path to file
     * @return string File URL
     */
    public function getFileUrl(string $relativePath): string
    {
        $uploadUrl = wp_upload_dir()['baseurl'];
        return $uploadUrl . '/' . $relativePath;
    }

    /**
     * Get file path based on access key and date
     * Organized like NFSE: YYYY/MM/archive/access_key.ext
     *
     * @param string $accessKey NFe access key (chave de acesso)
     * @param string $extension File extension (xml|pdf)
     * @param \DateTime $date Date for directory structure
     * @return string Relative path
     */
    private function getFilePath(string $accessKey, string $extension, \DateTime $date): string
    {
        $year = $date->format('Y');
        $month = $date->format('m');
        
        // Structure: nfe-files/YYYY/MM/archive/access_key.ext (same as NFSE)
        return sprintf(
            'nfe-files/%s/%s/archive/%s.%s',
            $year,
            $month,
            $accessKey,
            $extension
        );
    }

    /**
     * Get file path from access key (public method for external use)
     * Organized like NFSE: YYYY/MM/archive/access_key.ext
     *
     * @param string $accessKey NFe access key (chave de acesso)
     * @param string $extension File extension (xml|pdf)
     * @param \DateTime $date Date for directory structure
     * @return string Relative path
     */
    public function getFilePathFromAccessKey(string $accessKey, string $extension, \DateTime $date): string
    {
        return $this->getFilePath($accessKey, $extension, $date);
    }

    /**
     * Ensure secure NFe directory exists
     *
     * Creates .htaccess file to protect directory
     */
    private function ensureSecureDirectory(): void
    {
        // Ensure base directory exists
        $this->ensureDirectoryExists($this->nfeDir);
        
        // Create .htaccess to protect directory
        $htaccessPath = $this->nfeDir . '/.htaccess';
        
        if (!file_exists($htaccessPath)) {
            $htaccessContent = "Order Deny,Allow\nDeny from all\n";
            file_put_contents($htaccessPath, $htaccessContent);
            
            $this->log()->info('nfe.htaccess_created', ['path' => $htaccessPath]);
        }
        
        // Create index.php to prevent directory listing
        $indexPath = $this->nfeDir . '/index.php';
        
        if (!file_exists($indexPath)) {
            file_put_contents($indexPath, "<?php\n// Silence is golden\n");
        }
    }

    /**
     * Ensure directory exists
     *
     * @param string $path Directory path
     */
    private function ensureDirectoryExists(string $path): void
    {
        if (!is_dir($path)) {
            wp_mkdir_p($path);
            
            $this->log()->debug('nfe.directory_created', ['path' => $path]);
        }
    }

    /**
     * Cleanup old files
     *
     * @param int $daysOld Delete files older than this many days
     * @return int Number of files deleted
     */
    public function cleanupOldFiles(int $daysOld = 365): int
    {
        $deleted = 0;
        $cutoffTime = time() - ($daysOld * DAY_IN_SECONDS);
        
        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($this->nfeDir, \RecursiveDirectoryIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::CHILD_FIRST
        );
        
        foreach ($iterator as $file) {
            if ($file->isFile()) {
                $fileTime = $file->getMTime();
                
                if ($fileTime < $cutoffTime) {
                    if (unlink($file->getPathname())) {
                        $deleted++;
                    }
                }
            }
        }
        
        // Remove empty directories
        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($this->nfeDir, \RecursiveDirectoryIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::CHILD_FIRST
        );
        
        foreach ($iterator as $dir) {
            if ($dir->isDir() && count(scandir($dir->getPathname())) === 2) {
                // Only . and .. entries
                rmdir($dir->getPathname());
            }
        }
        
        if ($deleted > 0) {
            $this->log()->info('nfe.old_files_deleted', [
                'days_old' => $daysOld,
                'count' => $deleted,
            ]);
        }
        
        return $deleted;
    }

    /**
     * Get storage statistics
     *
     * @return array Storage statistics
     */
    public function getStorageStats(): array
    {
        $totalFiles = 0;
        $totalSize = 0;
        $xmlFiles = 0;
        $pdfFiles = 0;
        
        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($this->nfeDir, \RecursiveDirectoryIterator::SKIP_DOTS)
        );
        
        foreach ($iterator as $file) {
            if ($file->isFile()) {
                $totalFiles++;
                $totalSize += $file->getSize();
                
                if ($file->getExtension() === 'xml') {
                    $xmlFiles++;
                } elseif ($file->getExtension() === 'pdf') {
                    $pdfFiles++;
                }
            }
        }
        
        return [
            'total_files' => $totalFiles,
            'total_size' => $totalSize,
            'total_size_human' => size_format($totalSize),
            'xml_files' => $xmlFiles,
            'pdf_files' => $pdfFiles,
            'storage_path' => $this->nfeDir,
        ];
    }
}
