<?php

namespace Espo\Modules\SmartAssistant\Services;

use Espo\Core\Exceptions\Error;
use Espo\Core\InjectableFactory;
use Espo\Core\Utils\Log;
use Espo\ORM\EntityManager;
use Espo\Modules\NetworkStorageIntegration\Services\NetworkDocumentService;
use Espo\Modules\NetworkStorageIntegration\Classes\StorageClientInterface;

class DocumentAnalyzer
{
    private const DEFAULT_MAX_TEXT_LENGTH = 100000;
    private const MAX_FILE_SIZE = 10 * 1024 * 1024;
    private const TMP_DIR = 'data/tmp/assistant-docs';

    private EntityManager $entityManager;
    private InjectableFactory $injectableFactory;
    private Log $log;
    private ?int $maxTextLength = null;

    private const CATEGORY_PATTERNS = [
        'כתבי_טענות' => ['כתב_תביעה', 'כתב תביעה', 'כתב_הגנה', 'כתב הגנה', 'סיכומים', 'תביעה', 'הגנה', 'כתב_ערעור', 'כתב ערעור', 'בקשה', 'תגובה', 'תשובה'],
        'החלטות_בית_משפט' => ['החלטה', 'פסק_דין', 'פסק דין', 'גזר_דין', 'גזר דין', 'צו', 'פרוטוקול', 'פס"ד', 'גז"ד'],
        'חוזים' => ['חוזה', 'הסכם', 'נספח', 'תוספת'],
        'התכתבויות' => ['מכתב', 'פקס', 'fax', 'letter', 'דואר'],
        'מסמכי_זיהוי' => ['ת.ז.', 'ת"ז', 'תעודת_זהות', 'תעודת זהות', 'דרכון', 'רישיון', 'passport', 'תעודה'],
        'ייפויי_כוח' => ['ייפוי כוח', 'ייפוי_כוח', 'יפוי כח', 'יפוי_כח'],
        'חוות_דעת' => ['חוות_דעת', 'חוות דעת', 'חו"ד', 'חוד'],
        'מסמכים_מסיוע_משפטי' => ['סיוע משפטי', 'סיוע_משפטי', 'לשכת סיוע'],
        'מסמכים_מהביטוח_הלאומי' => ['ביטוח לאומי', 'ביטוח_לאומי', 'בל"ל', 'המוסד לביטוח'],
    ];

    public function __construct(EntityManager $entityManager, InjectableFactory $injectableFactory, Log $log)
    {
        $this->entityManager = $entityManager;
        $this->injectableFactory = $injectableFactory;
        $this->log = $log;
    }

    public function listDocumentsRecursive(string $caseId): array
    {
        $casePath = $this->getCaseFolderPath($caseId);
        if (!$casePath) return ['casePath' => null, 'totalFiles' => 0, 'folders' => [], 'rootFiles' => []];

        $client = $this->getStorageClient();
        $topLevel = $client->listFolder($casePath);
        $folders = []; $rootFiles = []; $totalFiles = 0;

        foreach ($topLevel as $item) {
            $isFolder = ($item['type'] ?? null) === 'folder' || !empty($item['isFolder']);
            if ($isFolder) {
                $subFiles = [];
                try {
                    foreach ($client->listFolder($item['path']) as $sub) {
                        $subIsFolder = ($sub['type'] ?? null) === 'folder' || !empty($sub['isFolder']);
                        if (!$subIsFolder) {
                            $subFiles[] = ['name' => $sub['name'], 'path' => $sub['path'], 'size' => $sub['size'] ?? null, 'mimeType' => $sub['mimeType'] ?? null, 'modified' => $sub['modified'] ?? null];
                            $totalFiles++;
                        }
                    }
                } catch (\Exception $e) {
                    $this->log->warning("SmartAssistant: Failed to list subfolder {$item['path']}: " . $e->getMessage());
                }
                $folders[] = ['name' => $item['name'], 'path' => $item['path'], 'files' => $subFiles];
            } else {
                $rootFiles[] = ['name' => $item['name'], 'path' => $item['path'], 'size' => $item['size'] ?? null, 'mimeType' => $item['mimeType'] ?? null, 'modified' => $item['modified'] ?? null];
                $totalFiles++;
            }
        }

        return ['casePath' => $casePath, 'totalFiles' => $totalFiles, 'folders' => $folders, 'rootFiles' => $rootFiles];
    }

    public function extractTextContent(string $filePath): ?string
    {
        $maxLen = $this->getMaxTextLength();
        $text = $this->extractFullText($filePath, $maxLen);
        return $text;
    }

    public function extractFullText(string $filePath, ?int $maxLength = null): ?string
    {
        $client = $this->getStorageClient();
        $filename = basename($filePath);

        try { $content = $client->downloadFile($filePath); } catch (\Exception $e) { return null; }

        if (strlen($content) > self::MAX_FILE_SIZE) return null;

        if (!is_dir(self::TMP_DIR)) mkdir(self::TMP_DIR, 0755, true);
        $tmpFile = self::TMP_DIR . '/' . uniqid('doc_') . '_' . $filename;
        file_put_contents($tmpFile, $content);

        $text = null;
        $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

        try {
            if ($ext === 'pdf') $text = $this->extractFromPdf($tmpFile);
            elseif (in_array($ext, ['docx', 'doc'])) $text = $this->extractFromDocx($tmpFile);
            elseif (in_array($ext, ['txt', 'csv', 'log'])) $text = $content;
        } catch (\Exception $e) {
            $this->log->warning("SmartAssistant: Text extraction failed: " . $e->getMessage());
        }

        if (file_exists($tmpFile)) unlink($tmpFile);
        if ($text === null) return null;

        $text = trim($text);
        if ($maxLength !== null && mb_strlen($text) > $maxLength) {
            $text = mb_substr($text, 0, $maxLength) . '...';
        }

        return $text;
    }

    public function extractMultipleDocuments(array $filePaths, int $maxPerFile = 50000): array
    {
        $results = [];

        foreach ($filePaths as $filePath) {
            $fileName = basename($filePath);
            try {
                $text = $this->extractFullText($filePath, $maxPerFile);
                $results[] = [
                    'path' => $filePath,
                    'fileName' => $fileName,
                    'text' => $text,
                    'charCount' => $text !== null ? mb_strlen($text) : 0,
                ];
            } catch (\Exception $e) {
                $results[] = [
                    'path' => $filePath,
                    'fileName' => $fileName,
                    'text' => null,
                    'charCount' => 0,
                    'error' => $e->getMessage(),
                ];
            }
        }

        return $results;
    }

    public function renameDocument(string $sourcePath, string $newName): array
    {
        $client = $this->getStorageClient();
        $parentPath = dirname($sourcePath);
        $oldName = basename($sourcePath);

        $oldExt = pathinfo($oldName, PATHINFO_EXTENSION);
        $newExt = pathinfo($newName, PATHINFO_EXTENSION);
        if ($oldExt && (!$newExt || mb_strtolower($newExt) !== mb_strtolower($oldExt))) {
            $newName = pathinfo($newName, PATHINFO_FILENAME) . '.' . $oldExt;
        }

        $destination = $parentPath . '/' . $newName;
        if ($sourcePath === $destination) return ['success' => true, 'oldPath' => $sourcePath, 'newPath' => $destination, 'oldName' => $oldName, 'newName' => $newName];

        $client->move($sourcePath, $destination);
        $this->updateDocumentEntity($sourcePath, $destination);

        return ['success' => true, 'oldPath' => $sourcePath, 'newPath' => $destination, 'oldName' => $oldName, 'newName' => $newName];
    }

    public function uploadDocument(string $caseId, string $fileName, string $content, ?string $subfolder = null): array
    {
        $casePath = $this->getCaseFolderPath($caseId);
        if (!$casePath) {
            throw new Error('Case does not have a document folder configured (networkStorageFolderPath).');
        }

        $client = $this->getStorageClient();

        $targetDir = $casePath;
        if ($subfolder) {
            $targetDir = $casePath . '/' . trim($subfolder, '/');
            if (!$client->exists($targetDir)) {
                $client->createFolderRecursive($targetDir);
            }
        }

        $targetPath = $targetDir . '/' . $fileName;
        $client->uploadFile($targetPath, $content);

        return [
            'path' => $targetPath,
            'fileName' => $fileName,
            'folder' => $subfolder,
        ];
    }

    public function getCaseFolderPath(string $caseId): ?string
    {
        $case = $this->entityManager->getEntityById('Case', $caseId);
        if (!$case) return null;

        // Try stored path first
        $storedPath = $case->get('networkStorageFolderPath') ?: null;
        if ($storedPath) return $storedPath;

        // Fall back to computed path from NetworkDocumentService
        try {
            $nds = $this->getNetworkDocumentService();
            return $nds->getEntityFolderPath('Case', $caseId);
        } catch (\Exception $e) {
            $this->log->warning("SmartAssistant: Failed to get case folder path: " . $e->getMessage());
            return null;
        }
    }

    private function getMaxTextLength(): int
    {
        if ($this->maxTextLength !== null) {
            return $this->maxTextLength;
        }

        try {
            $integration = $this->entityManager->getEntityById('Integration', 'SmartAssistant');
            if ($integration && $integration->get('enabled')) {
                $data = (array) ($integration->get('data') ?? []);
                $this->maxTextLength = (int) ($data['maxDocumentChars'] ?? self::DEFAULT_MAX_TEXT_LENGTH);
                return $this->maxTextLength;
            }
        } catch (\Exception $e) {
            // Ignore
        }

        $this->maxTextLength = self::DEFAULT_MAX_TEXT_LENGTH;
        return $this->maxTextLength;
    }

    private function getStorageClient(): StorageClientInterface
    {
        return $this->getNetworkDocumentService()->getClient();
    }

    private function getNetworkDocumentService(): NetworkDocumentService
    {
        return $this->injectableFactory->create(NetworkDocumentService::class);
    }

    private function extractFromPdf(string $tmpFile): ?string
    {
        $outputFile = $tmpFile . '.txt';
        exec("pdftotext -layout " . escapeshellarg($tmpFile) . " " . escapeshellarg($outputFile) . " 2>&1", $output, $returnCode);
        if ($returnCode !== 0 || !file_exists($outputFile)) return null;
        $text = file_get_contents($outputFile);
        unlink($outputFile);
        return $text ?: null;
    }

    private function extractFromDocx(string $tmpFile): ?string
    {
        if (!class_exists(\PhpOffice\PhpWord\IOFactory::class)) return null;
        $phpWord = \PhpOffice\PhpWord\IOFactory::load($tmpFile);
        $text = '';
        foreach ($phpWord->getSections() as $section) {
            foreach ($section->getElements() as $element) {
                if (method_exists($element, 'getText')) $text .= $element->getText() . "\n";
                elseif (method_exists($element, 'getElements')) {
                    foreach ($element->getElements() as $child) {
                        if (method_exists($child, 'getText')) $text .= $child->getText() . "\n";
                    }
                }
            }
        }
        return $text ?: null;
    }

    private function updateDocumentEntity(string $oldPath, string $newPath): void
    {
        try {
            $doc = $this->entityManager->getRDBRepository('Document')->where(['networkStoragePath' => $oldPath])->findOne();
            if ($doc) {
                $doc->set('networkStoragePath', $newPath);
                $doc->set('networkStorageFileName', basename($newPath));
                $this->entityManager->saveEntity($doc);
            }
        } catch (\Exception $e) {}
    }
}
