<?php

namespace App\Domain\Telegram\Actions;

use App\Domain\Assistant\Actions\SendAssistantMessageAction;
use App\Domain\Assistant\Models\AssistantConversation;
use App\Domain\Project\Models\ProjectRun;
use App\Domain\Shared\Models\Team;
use App\Domain\Telegram\Models\TelegramBot;
use App\Domain\Telegram\Models\TelegramChatBinding;
use App\Models\User;
use Illuminate\Support\Facades\Log;

class ProcessTelegramMessageAction
{
    public function __construct(
        private readonly SendAssistantMessageAction $sendAssistantMessage,
        private readonly SendTelegramReplyAction $sendReply,
        private readonly SendTypingIndicatorAction $sendTypingIndicator,
    ) {}

    /**
     * Process an inbound Telegram message: find/create conversation binding,
     * run the assistant, and send the reply.
     */
    public function execute(TelegramBot $bot, string $chatId, string $text, ?string $username = null): void
    {
        // Handle special commands
        $reply = $this->handleCommand($text, $bot);
        if ($reply !== null) {
            $this->sendReply->execute($bot->bot_token, $chatId, $reply);

            return;
        }

        // Find or create conversation binding for this chat
        $binding = TelegramChatBinding::withoutGlobalScopes()
            ->where('team_id', $bot->team_id)
            ->where('chat_id', $chatId)
            ->first();

        if (! $binding) {
            $binding = $this->createBinding($bot, $chatId, $username);
        }

        // Get or create conversation
        /** @var AssistantConversation|null $conversation */
        $conversation = $binding->conversation;
        if (! $conversation) {
            $conversation = $this->createConversation($bot, $username);
            $binding->update(['conversation_id' => $conversation->id]);
            $binding->refresh();
        }

        // Resolve user: prefer bound user, fall back to team owner
        /** @var User|null $user */
        $user = $binding->user ?? $this->resolveTeamUser($bot->team_id);
        if (! $user) {
            Log::warning('ProcessTelegramMessageAction: no user found', ['team_id' => $bot->team_id]);
            $this->sendReply->execute($bot->bot_token, $chatId, 'Error: no team user found.');

            return;
        }

        // Send typing indicator for potentially slow LLM responses
        $this->sendTypingIndicator->execute($bot, $chatId);

        // Send placeholder message that we'll edit live as tokens arrive.
        // Returns null if the send fails (network error, etc.) — we fall back to a final send.
        $placeholderMessageId = $this->sendReply->sendPlaceholder($bot->bot_token, $chatId);

        // Throttle: Telegram allows max 20 edits/minute/chat (1 per 3s is safe; 1.5s is within limit)
        $editIntervalMs = 1500;
        $lastEditAt = 0.0;
        $accumulated = '';

        try {
            $response = $this->sendAssistantMessage->executeStreaming(
                conversation: $conversation,
                userMessage: $text,
                user: $user,
                contextType: 'telegram',
                contextId: $chatId,
                onChunk: function (string $chunk) use (
                    $bot, $chatId, $placeholderMessageId, $editIntervalMs,
                    &$accumulated, &$lastEditAt
                ): void {
                    $accumulated .= $chunk;

                    if ($placeholderMessageId === null) {
                        return;
                    }

                    $nowMs = microtime(true) * 1000;
                    if (($nowMs - $lastEditAt) >= $editIntervalMs) {
                        $this->sendReply->editMessage($bot->bot_token, $chatId, $placeholderMessageId, $accumulated);
                        $lastEditAt = $nowMs;
                    }
                },
            );

            $replyText = $response->content ?: 'I could not process that request.';
        } catch (\Throwable $e) {
            Log::error('ProcessTelegramMessageAction: assistant error', [
                'chat_id' => $chatId,
                'error' => $e->getMessage(),
            ]);
            $replyText = 'I encountered an error processing your message. Please try again.';
        }

        // Update last message timestamp on bot
        $bot->update(['last_message_at' => now()]);

        if ($placeholderMessageId !== null) {
            // Final edit — sets the complete response on the placeholder message
            $this->sendReply->editMessage($bot->bot_token, $chatId, $placeholderMessageId, $replyText);
        } else {
            // Fallback: placeholder failed, send a new message
            $this->sendReply->execute($bot->bot_token, $chatId, $replyText);
        }
    }

    private function handleCommand(string $text, TelegramBot $bot): ?string
    {
        $command = strtolower(trim($text));

        if (str_starts_with($command, '/start')) {
            $botName = $bot->bot_name ?? config('app.name').' Bot';

            return "<b>Welcome to {$botName}!</b>\n\nI'm connected to your ".config('app.name')." workspace. You can:\n• Chat with your AI assistant\n• Ask about your projects and experiments\n• Get status updates\n\nType /help for more commands.";
        }

        if ($command === '/help') {
            return "<b>Available commands:</b>\n\n/start — Show welcome message\n/help — Show this help\n/status — Show last 3 project runs\n\nOr just type any message to chat with your assistant.";
        }

        if ($command === '/status') {
            $runs = ProjectRun::withoutGlobalScopes()
                ->where('project_runs.id', function ($query) use ($bot) {
                    $query->from('project_runs')
                        ->whereExists(function ($q) use ($bot) {
                            $q->from('projects')
                                ->whereColumn('projects.id', 'project_runs.project_id')
                                ->where('projects.team_id', $bot->team_id);
                        })
                        ->select('id');
                })
                ->with('project')
                ->latest()
                ->limit(3)
                ->get();

            if ($runs->isEmpty()) {
                return 'No recent project runs found.';
            }

            $lines = ["<b>Last 3 project runs:</b>\n"];
            foreach ($runs as $run) {
                /** @var ProjectRun $run */
                $status = $run->status instanceof \BackedEnum ? $run->status->value : (string) $run->status;
                $lines[] = "• <b>{$run->project?->title}</b> — {$status} ({$run->created_at->diffForHumans()})";
            }

            return implode("\n", $lines);
        }

        return null;
    }

    private function createBinding(TelegramBot $bot, string $chatId, ?string $username): TelegramChatBinding
    {
        return TelegramChatBinding::create([
            'team_id' => $bot->team_id,
            'chat_id' => $chatId,
            'user_id' => null,
            'conversation_id' => null,
        ]);
    }

    private function createConversation(TelegramBot $bot, ?string $username): AssistantConversation
    {
        return AssistantConversation::create([
            'team_id' => $bot->team_id,
            'user_id' => null,
            'title' => 'Telegram chat'.($username ? " (@{$username})" : ''),
            'context_type' => 'telegram',
            'context_id' => null,
        ]);
    }

    private function resolveTeamUser(string $teamId): ?User
    {
        /** @var User|null $owner */
        $owner = Team::find($teamId)?->owner;

        return $owner;
    }
}
