<?php

namespace Espo\Modules\SmartAssistant\Services;

use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\InjectableFactory;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Log;
use Espo\ORM\EntityManager;
use Espo\Entities\Note;
use Espo\Entities\User;

class SmartAssistantService
{
    public const NOTE_TYPE_REQUEST = 'SmartRequest';
    public const NOTE_TYPE_RESPONSE = 'SmartResponse';
    public const NOTE_TYPE_ACTION = 'SmartAction';

    // All tools execute inline — Shira has full permissions
    private const TOOLS_REQUIRING_CASE = [
        'change_status', 'schedule_hearing', 'list_documents', 'analyze_document',
        'read_document', 'read_multiple_documents', 'upload_document',
        'generate_document', 'merge_documents', 'save_memory',
        'batch_rename_documents',
    ];

    // Tools whose output is fed back to the AI for a follow-up webhook call
    private const AGENTIC_TOOLS = ['analyze_document', 'read_document', 'read_multiple_documents', 'list_documents'];

    private const DEFAULT_MAX_AGENTIC_LOOPS = 3;

    private EntityManager $entityManager;
    private InjectableFactory $injectableFactory;
    private Config $config;
    private Log $log;
    private User $user;

    private static array $conversations = [];

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

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

    public function chat(string $message, string $mode, ?string $caseId, ?string $conversationId): array
    {
        $userId = $this->user->getId();

        // Build context based on mode
        if ($mode === 'case' && $caseId) {
            $contextBuilder = $this->injectableFactory->create(CaseContextBuilder::class);
            $context = $contextBuilder->buildContext($caseId, $userId);

            // Add case memory to context
            $memoryProvider = $this->injectableFactory->create(CaseMemoryContextProvider::class);
            $context['caseMemory'] = $memoryProvider->getContextForCase($caseId);
            $context['mode'] = 'case';
        } else {
            $contextBuilder = $this->injectableFactory->create(OfficeContextBuilder::class);
            $context = $contextBuilder->buildSummaryContext($this->user, $this->getThresholds());

            // Auto-detect case drill-down
            $drillDown = $this->detectDrillDown($message);
            if ($drillDown) {
                $context['drillDown'] = $drillDown;
            }
        }

        // Load behavioral rules
        $ruleProvider = $this->injectableFactory->create(AssistantRuleContextProvider::class);
        $context['assistantRules'] = $ruleProvider->getRulesForMode($mode === 'case' ? 'case' : 'office');

        // Generate conversation ID
        if (!$conversationId) {
            $prefix = $mode === 'case' ? 'conv_' : 'oconv_';
            $conversationId = uniqid($prefix, true);
        }

        // Persist conversation
        try {
            $convRepo = $this->getConversationRepo();
            $convRepo->startOrResume($mode, $conversationId, $caseId);
            $convRepo->appendMessage($conversationId, 'user', $message);
        } catch (\Exception $e) {
            $this->log->warning('SmartAssistant: Failed to persist conversation: ' . $e->getMessage());
        }

        // Create stream note (only for case mode)
        if ($mode === 'case' && $caseId) {
            $this->createStreamNote(self::NOTE_TYPE_REQUEST, $message, $caseId, [
                'conversationId' => $conversationId,
            ]);
        }

        // Load conversation history for context
        $conversationHistory = [];
        try {
            $conversationHistory = $this->getConversationRepo()->getMessages($conversationId);
        } catch (\Exception $e) {
            // Ignore
        }

        // Resolve effective caseId: explicit case mode, or drill-down detection in office mode
        $effectiveCaseId = $caseId;
        if (!$effectiveCaseId && !empty($context['drillDown']['case']['id'])) {
            $effectiveCaseId = $context['drillDown']['case']['id'];
        }

        // Execute tools with agentic loop — read tools feed results back to AI
        $loopResult = $this->executeActionsWithLoop(
            $message, $context, $conversationId, $conversationHistory,
            $mode, $effectiveCaseId, $userId
        );

        $responseText = $loopResult['responseText'];
        $executedActions = $loopResult['executedActions'];

        // Persist response
        try {
            $this->getConversationRepo()->appendMessage($conversationId, 'assistant', $responseText, $executedActions);
        } catch (\Exception $e) {
            $this->log->warning('SmartAssistant: Failed to persist response: ' . $e->getMessage());
        }

        // Stream note for case mode
        if ($mode === 'case' && $caseId) {
            $this->createStreamNote(self::NOTE_TYPE_RESPONSE, $responseText, $caseId, [
                'conversationId' => $conversationId,
                'actions' => $executedActions,
            ]);
        }

        return [
            'conversationId' => $conversationId,
            'text' => $responseText,
            'actions' => [],
            'executedActions' => $executedActions,
        ];
    }

    private function executeActionsWithLoop(
        string $message,
        array $context,
        string $conversationId,
        array $conversationHistory,
        string $mode,
        ?string $effectiveCaseId,
        string $userId
    ): array {
        $agenticEnabled = $this->getIntegrationSetting('agenticLoopEnabled', true);
        $maxLoops = (int) $this->getIntegrationSetting('maxAgenticLoops', self::DEFAULT_MAX_AGENTIC_LOOPS);
        $executor = $this->injectableFactory->create(ActionExecutor::class);

        $loopCount = 0;
        $allExecutedActions = [];
        $currentHistory = $conversationHistory;
        $finalResponseText = '';

        while ($loopCount <= $maxLoops) {
            // Call webhook (first call uses original message, subsequent calls use empty message)
            $currentMessage = $loopCount === 0 ? $message : '';
            $response = $this->callWebhook($currentMessage, $context, $conversationId, $currentHistory, $mode);

            $actions = $response['actions'] ?? [];
            $responseText = $response['text'] ?? '';

            // If no actions, we're done
            if (empty($actions)) {
                $finalResponseText = $responseText;
                break;
            }

            $agenticResults = [];
            $inlineResults = [];

            foreach ($actions as $action) {
                $tool = $action['tool'] ?? '';
                $actionCaseId = $effectiveCaseId;

                if (!$actionCaseId && !empty($action['params']['caseId'])) {
                    $actionCaseId = $action['params']['caseId'];
                }

                if (!$actionCaseId && in_array($tool, self::TOOLS_REQUIRING_CASE, true)) {
                    $inlineResults[] = "⚠️ לא ניתן לבצע {$tool} — לא זוהה תיק. נסה מתוך מסך התיק.";
                    continue;
                }

                try {
                    $result = $executor->execute($tool, $action['params'] ?? [], $actionCaseId, $userId);

                    $allExecutedActions[] = [
                        'tool' => $tool,
                        'status' => 'executed',
                        'entityType' => $result['entityType'] ?? null,
                        'entityId' => $result['entityId'] ?? null,
                    ];

                    if ($actionCaseId) {
                        $this->createStreamNote(self::NOTE_TYPE_ACTION, $result['message'] ?? 'פעולה בוצעה', $actionCaseId, [
                            'conversationId' => $conversationId,
                            'status' => 'executed',
                            'entityType' => $result['entityType'] ?? null,
                            'entityId' => $result['entityId'] ?? null,
                        ]);
                    }

                    // Separate agentic tools (need re-invocation) from inline tools
                    if ($agenticEnabled && in_array($tool, self::AGENTIC_TOOLS, true)) {
                        $agenticResults[] = [
                            'tool' => $tool,
                            'params' => $action['params'] ?? [],
                            'result' => $result['message'] ?? '',
                        ];
                    } else {
                        if (!empty($result['message'])) {
                            $inlineResults[] = $result['message'];
                        }
                    }
                } catch (\Exception $e) {
                    $this->log->warning("SmartAssistant: Execution of {$tool} failed: " . $e->getMessage());
                    $inlineResults[] = "שגיאה בביצוע {$tool}: " . $e->getMessage();
                }
            }

            // If no agentic results, we're done — return response with inline results
            if (empty($agenticResults)) {
                $finalResponseText = $responseText;
                if (!empty($inlineResults)) {
                    $finalResponseText .= "\n\n" . implode("\n\n", $inlineResults);
                }
                break;
            }

            // Feed agentic results back to AI for next iteration
            $this->log->debug("SmartAssistant: Agentic loop iteration " . ($loopCount + 1) . " — " . count($agenticResults) . " tool results to feed back");

            // Add the AI response to history
            $currentHistory[] = ['role' => 'assistant', 'content' => $responseText];

            // Add each tool result to history
            foreach ($agenticResults as $ar) {
                $currentHistory[] = [
                    'role' => 'tool_result',
                    'toolName' => $ar['tool'],
                    'content' => $ar['result'],
                ];
            }

            // Also append any inline results
            if (!empty($inlineResults)) {
                $currentHistory[] = [
                    'role' => 'tool_result',
                    'toolName' => '_inline_actions',
                    'content' => implode("\n\n", $inlineResults),
                ];
            }

            $loopCount++;
        }

        // Safety: if we exhausted the loop, use the last response
        if ($loopCount > $maxLoops && empty($finalResponseText)) {
            $finalResponseText = $response['text'] ?? 'הגעתי למגבלת הסיבובים. אנא נסה שוב.';
        }

        return [
            'responseText' => $finalResponseText,
            'executedActions' => $allExecutedActions,
        ];
    }

    private function getIntegrationSetting(string $key, $default = null)
    {
        $integration = $this->entityManager->getEntityById('Integration', 'SmartAssistant');
        if ($integration && $integration->get('enabled')) {
            $data = (array) ($integration->get('data') ?? []);
            return $data[$key] ?? $default;
        }
        return $default;
    }

    public function executeAction(string $conversationId, string $actionId, bool $approved): array
    {
        $conversation = $this->loadConversation($conversationId);

        if (!$conversation) {
            throw new NotFound("Conversation {$conversationId} not found.");
        }

        $caseId = $conversation['caseId'];
        $userId = $conversation['userId'];
        $actions = $conversation['actions'] ?? [];

        $action = null;
        foreach ($actions as $a) {
            if (($a['actionId'] ?? '') === $actionId) {
                $action = $a;
                break;
            }
        }

        if (!$action) {
            throw new NotFound("Action {$actionId} not found.");
        }

        if (!$approved) {
            if ($caseId) {
                $this->createStreamNote(self::NOTE_TYPE_ACTION, 'פעולה נדחתה: ' . ($action['displayText'] ?? ''), $caseId, [
                    'conversationId' => $conversationId,
                    'actionId' => $actionId,
                    'status' => 'rejected',
                ]);
            }

            return ['success' => true, 'status' => 'rejected', 'message' => 'הפעולה נדחתה'];
        }

        $executor = $this->injectableFactory->create(ActionExecutor::class);

        try {
            $result = $executor->execute($action['tool'], $action['params'] ?? [], $caseId, $userId);

            if ($caseId) {
                $this->createStreamNote(self::NOTE_TYPE_ACTION, $result['message'] ?? 'פעולה בוצעה', $caseId, [
                    'conversationId' => $conversationId,
                    'actionId' => $actionId,
                    'status' => 'executed',
                    'entityType' => $result['entityType'] ?? null,
                    'entityId' => $result['entityId'] ?? null,
                ]);
            }

            return $result;
        } catch (\Exception $e) {
            $this->log->error("SmartAssistant: Action execution failed: " . $e->getMessage());

            if ($caseId) {
                $this->createStreamNote(self::NOTE_TYPE_ACTION, 'שגיאה: ' . $e->getMessage(), $caseId, [
                    'conversationId' => $conversationId,
                    'actionId' => $actionId,
                    'status' => 'error',
                ]);
            }

            return ['success' => false, 'message' => 'שגיאה בביצוע הפעולה: ' . $e->getMessage()];
        }
    }

    public function getStatus(): array
    {
        $webhookUrl = $this->getWebhookUrl();

        return [
            'enabled' => $webhookUrl !== null,
            'webhookConfigured' => !empty($webhookUrl),
        ];
    }

    public function getSummary(): array
    {
        $alertCalc = $this->injectableFactory->create(AlertCalculator::class);
        $thresholds = $this->getThresholds();
        $hearingDays = $thresholds['upcomingHearingDays'] ?? 7;

        return [
            'summary' => $alertCalc->getSummary(),
            'alerts' => $alertCalc->calculateAlerts($thresholds),
            'openCases' => $alertCalc->getOpenCaseItems(),
            'upcomingHearings' => $alertCalc->getUpcomingHearingItems($hearingDays),
        ];
    }

    public function getAlerts(): array
    {
        $alertCalc = $this->injectableFactory->create(AlertCalculator::class);
        return $alertCalc->calculateAlerts($this->getThresholds());
    }

    public function getHistory(?string $caseId): array
    {
        if ($caseId) {
            // Case mode: return stream notes
            $notes = $this->entityManager->getRDBRepository('Note')
                ->where([
                    'parentType' => 'Case',
                    'parentId' => $caseId,
                    'type' => [self::NOTE_TYPE_REQUEST, self::NOTE_TYPE_RESPONSE, self::NOTE_TYPE_ACTION],
                ])
                ->order('createdAt', 'DESC')
                ->limit(0, 50)
                ->find();

            $history = [];
            foreach ($notes as $note) {
                $history[] = [
                    'id' => $note->get('id'),
                    'type' => $note->get('type'),
                    'post' => $note->get('post'),
                    'data' => $note->get('data') ?? (object) [],
                    'createdAt' => $note->get('createdAt'),
                    'createdByName' => $note->get('createdByName'),
                ];
            }

            return ['list' => array_reverse($history), 'total' => count($history)];
        }

        // Office mode: return latest conversation from today
        $conversations = $this->getConversationRepo()->getRecentConversations('office', null, 1);

        if (empty($conversations)) return [];

        $latest = $conversations[0];
        $messages = $this->getConversationRepo()->getMessages($latest['conversationKey']);

        return [
            'conversationId' => $latest['conversationKey'],
            'messages' => $messages,
            'updatedAt' => $latest['lastMessageAt'],
        ];
    }

    public function getConversations(?string $type): array
    {
        return $this->getConversationRepo()->getRecentConversations($type, null, 50);
    }

    public function getConversationMessages(string $key): array
    {
        return [
            'conversationId' => $key,
            'messages' => $this->getConversationRepo()->getMessages($key),
        ];
    }

    public function searchHistory(string $query): array
    {
        return $this->getConversationRepo()->searchConversations($query);
    }

    private function detectDrillDown(string $message): ?array
    {
        $cases = $this->entityManager->getRDBRepository('Case')
            ->select(['id', 'name'])
            ->where([
                'status!=' => ['ClosedSettlement', 'ClosedJudgment', 'Closed', 'Rejected'],
                'deleted' => false,
            ])
            ->find();

        foreach ($cases as $case) {
            if (mb_stripos($message, $case->get('name')) !== false) {
                $contextBuilder = $this->injectableFactory->create(OfficeContextBuilder::class);
                return $contextBuilder->buildCaseDrillDown($case->get('id'), $this->user);
            }
        }

        return null;
    }

    private function callWebhook(string $message, array $context, string $conversationId, array $history, string $mode): array
    {
        $webhookUrl = $this->getWebhookUrl();

        if (!$webhookUrl) {
            throw new Error('SmartAssistant webhook URL is not configured. Go to Admin > Integrations > Smart Assistant.');
        }

        $siteUrl = $this->config->get('siteUrl') ?? '';

        $payload = json_encode([
            'message' => $message,
            'context' => $context,
            'conversationId' => $conversationId,
            'conversationHistory' => $history,
            'mode' => $mode,
            'systemInstructions' => $this->getSystemInstructions(),
            'espocrm' => [
                'url' => rtrim($siteUrl, '/'),
                'apiKey' => $this->getEspocrmApiKey(),
            ],
        ]);

        $headers = ['Content-Type: application/json', 'Accept: application/json'];

        $apiKey = $this->getApiKey();
        if ($apiKey) {
            $headers[] = 'X-Api-Key: ' . $apiKey;
        }

        $ch = curl_init($webhookUrl);
        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $payload,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_TIMEOUT => 180,
            CURLOPT_CONNECTTIMEOUT => 10,
        ]);

        $responseBody = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($error) {
            $this->log->error("SmartAssistant: Webhook error: {$error}");
            throw new Error("Failed to connect to assistant: {$error}");
        }

        if ($httpCode !== 200) {
            $this->log->error("SmartAssistant: Webhook HTTP {$httpCode}: {$responseBody}");
            throw new Error("Assistant returned error (HTTP {$httpCode})");
        }

        $response = json_decode($responseBody, true);
        if (!$response) {
            throw new Error('Invalid response from assistant');
        }

        return $response;
    }

    private function createStreamNote(string $type, string $content, string $caseId, array $data = []): void
    {
        $note = $this->entityManager->getNewEntity('Note');
        $note->set([
            'type' => $type,
            'post' => $content,
            'parentType' => 'Case',
            'parentId' => $caseId,
            'data' => (object) $data,
        ]);
        $this->entityManager->saveEntity($note);
    }

    private function getWebhookUrl(): ?string
    {
        $integration = $this->entityManager->getEntityById('Integration', 'SmartAssistant');
        if ($integration && $integration->get('enabled')) {
            $data = $integration->get('data') ?? (object) [];
            if (!empty($data->webhookUrl)) {
                return $data->webhookUrl;
            }
        }
        return null;
    }

    private function getApiKey(): ?string
    {
        $integration = $this->entityManager->getEntityById('Integration', 'SmartAssistant');
        if ($integration && $integration->get('enabled')) {
            $data = $integration->get('data') ?? (object) [];
            if (!empty($data->apiKey)) {
                return $data->apiKey;
            }
        }
        return null;
    }

    private function getEspocrmApiKey(): ?string
    {
        $integration = $this->entityManager->getEntityById('Integration', 'SmartAssistant');
        if ($integration && $integration->get('enabled')) {
            $data = $integration->get('data') ?? (object) [];
            if (!empty($data->espocrmApiKey)) {
                return $data->espocrmApiKey;
            }
        }
        return null;
    }

    private function getSystemInstructions(): array
    {
        return [
            'call_vs_meeting' => implode(' ', [
                'כשהמשתמש מדווח ששוחח, דיבר, התקשר, או שוחחה עם לקוח/ה — השתמש בכלי create_call (לא create_meeting).',
                'כשהמשתמש מדווח שנפגש, קבע פגישה, או תיאם מפגש — השתמש בכלי create_meeting.',
                'שיחת טלפון = create_call. פגישה פיזית/זום = create_meeting.',
            ]),
            'open_questions' => implode(' ', [
                'אם שאלת את המשתמש שאלה והוא לא ענה עליה בתגובה הבאה — חזור על השאלה בתחילת התשובה הבאה.',
                'עקוב אחרי שאלות פתוחות ואל תוותר עליהן עד שהמשתמש עונה או אומר לך לוותר.',
            ]),
            'behavioral_rules' => implode(' ', [
                'אם context.assistantRules מכיל כללים — יש ליישם אותם בכל שיחה.',
                'כללים גלובליים תקפים תמיד. כללי "case" תקפים רק במצב תיק. כללי "office" תקפים רק במצב משרד.',
                'אם המשתמש מבקש להגדיר כלל חדש (למשל "תמיד תעשה X כש-Y") — השתמש בכלי save_rule כדי לשמור אותו.',
            ]),
        ];
    }

    private function getThresholds(): array
    {
        $integration = $this->entityManager->getEntityById('Integration', 'SmartAssistant');
        if ($integration && $integration->get('enabled')) {
            $data = (array) ($integration->get('data') ?? []);
            return [
                'inactivityWarningDays' => $data['inactivityWarningDays'] ?? 14,
                'inactivityCriticalDays' => $data['inactivityCriticalDays'] ?? 30,
                'upcomingHearingDays' => $data['upcomingHearingDays'] ?? 7,
            ];
        }
        return [];
    }

    private function saveConversationFile(string $conversationId, ?string $caseId, string $userId, array $actions): void
    {
        $dir = 'data/tmp/assistant-conversations';
        if (!is_dir($dir)) mkdir($dir, 0755, true);
        file_put_contents($dir . '/' . $conversationId . '.json', json_encode([
            'caseId' => $caseId,
            'userId' => $userId,
            'actions' => $actions,
            'createdAt' => date('Y-m-d H:i:s'),
        ]));
    }

    private function loadConversation(string $conversationId): ?array
    {
        if (isset(self::$conversations[$conversationId])) {
            return self::$conversations[$conversationId];
        }

        $file = 'data/tmp/assistant-conversations/' . basename($conversationId) . '.json';
        if (file_exists($file)) {
            $data = json_decode(file_get_contents($file), true);
            if (isset($data['createdAt']) && (time() - strtotime($data['createdAt'])) > 86400) {
                unlink($file);
                return null;
            }
            return $data;
        }

        return null;
    }
}
