<?php

declare(strict_types=1);

namespace App\Services\AiAssistant;

use App\Models\AiConversation;
use App\Models\AiMessage;
use App\Models\User;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Prism\Prism\Enums\StreamEventType;
use Prism\Prism\Facades\Prism;
use Prism\Prism\ValueObjects\Messages\UserMessage;
use Symfony\Component\HttpFoundation\StreamedResponse;

class AiAssistantService
{
    public function __construct(
        protected Context\ConversationContext $conversationContext,
        protected Context\TenantContext $tenantContext,
        protected ToolManager $toolManager,
        protected Prompts\SystemPrompt $systemPrompt,
    ) {}

    public function chat(
        string $message,
        ?string $conversationUlid = null,
        ?User $user = null
    ): array {
        $user ??= auth()->user();

        $conversation = $this->getOrCreateConversation($conversationUlid, $user);
        $userMessage = $this->saveUserMessage($conversation, $message);

        $context = $this->conversationContext->build($conversation);
        $systemPrompt = $this->systemPrompt->build();

        $messages = [
            new \Prism\Prism\ValueObjects\Messages\SystemMessage($systemPrompt),
            ...$context['messages'],
            new UserMessage($message),
        ];

        try {
            $provider = config('prism.default_provider', 'deepseek');
            $model = config("prism.models.{$provider}");

            $response = Prism::text()
                ->using($provider, $model)
                ->withMessages($messages)
                ->withTools($this->toolManager->getAvailableTools())
                ->withMaxSteps(config('prism.max_steps', 5))
                ->asText();

            return $this->processResponse($response, $conversation, $userMessage);
        } catch (\Throwable $e) {
            Log::error('AI chat error', ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);

            return $this->handleError($e, $conversation);
        }
    }

    public function stream(
        string $message,
        ?string $conversationUlid = null,
        ?User $user = null
    ): StreamedResponse {
        $user ??= auth()->user();

        $conversation = $this->getOrCreateConversation($conversationUlid, $user);
        $userMessage = $this->saveUserMessage($conversation, $message);

        $context = $this->conversationContext->build($conversation);
        $systemPrompt = $this->systemPrompt->build();

        $messages = [
            new \Prism\Prism\ValueObjects\Messages\SystemMessage($systemPrompt),
            ...$context['messages'],
            new UserMessage($message),
        ];

        $provider = config('prism.default_provider', 'deepseek');
        $model = config("prism.models.{$provider}");
        $maxSteps = config('prism.max_steps', 5);

        return new StreamedResponse(
            function () use ($messages, $provider, $model, $maxSteps, $conversation, $userMessage) {
                $fullContent = '';
                $toolCalls = [];

                try {
                    Log::info('Starting AI stream', [
                        'provider' => $provider,
                        'model' => $model,
                        'messages_count' => count($messages),
                        'tools_count' => count($this->toolManager->getAvailableTools()),
                    ]);

                    $stream = Prism::text()
                        ->using($provider, $model)
                        ->withMessages($messages)
                        ->withTools($this->toolManager->getAvailableTools())
                        ->withMaxSteps($maxSteps)
                        ->asStream();

                    foreach ($stream as $event) {
                        Log::debug('AI stream event', ['type' => $event->type()]);
                        // Handle text delta events (content chunks)
                        if ($event->type() === StreamEventType::TextDelta) {
                            $fullContent .= $event->delta;
                            echo 'data: '.json_encode(['type' => 'content', 'content' => $event->delta])."\n\n";
                            ob_flush();
                            flush();
                        }

                        // Handle tool call events
                        if ($event->type() === StreamEventType::ToolCall) {
                            $toolCalls[] = [
                                'id' => $event->toolCall->id,
                                'name' => $event->toolCall->name,
                                'arguments' => $event->toolCall->arguments(),
                            ];
                            echo 'data: '.json_encode([
                                'type' => 'tool_call',
                                'tool' => $event->toolCall->name,
                                'arguments' => $event->toolCall->arguments(),
                            ])."\n\n";
                            ob_flush();
                            flush();
                        }

                        // Handle tool result events
                        if ($event->type() === StreamEventType::ToolResult) {
                            echo 'data: '.json_encode([
                                'type' => 'tool_result',
                                'tool' => $event->toolResult->toolName,
                                'result' => $event->toolResult->result,
                            ])."\n\n";
                            ob_flush();
                            flush();
                        }

                        // Check for stream end
                        if ($event->type() === StreamEventType::StreamEnd) {
                            break;
                        }
                    }

                    $this->saveAssistantMessage($conversation, $fullContent, $userMessage, toolCalls: $toolCalls);

                    echo 'data: '.json_encode(['type' => 'done', 'conversation_id' => $conversation->ulid])."\n\n";
                    ob_flush();
                    flush();
                } catch (\Throwable $e) {
                    Log::error('AI stream error', [
                        'message' => $e->getMessage(),
                        'trace' => $e->getTraceAsString(),
                    ]);
                    echo 'data: '.json_encode(['type' => 'error', 'error' => $e->getMessage()])."\n\n";
                    ob_flush();
                    flush();
                }
            },
            200,
            [
                'Content-Type' => 'text/event-stream',
                'Cache-Control' => 'no-cache',
                'X-Accel-Buffering' => 'no',
            ]
        );
    }

    public function getConversations(?User $user = null): array
    {
        $user ??= auth()->user();

        return AiConversation::query()
            ->when($user, fn ($q) => $q->where('user_id', $user->id))
            ->orderByDesc('last_message_at')
            ->get()
            ->map(fn ($c) => [
                'id' => $c->ulid,
                'title' => $c->title ?? 'New Conversation',
                'last_message_at' => $c->last_message_at?->toIso8601String(),
                'message_count' => $c->messages()->count(),
            ])
            ->toArray();
    }

    public function getConversationMessages(string $ulid): array
    {
        $conversation = AiConversation::where('ulid', $ulid)->firstOrFail();

        return $conversation->messages
            ->map(fn ($m) => [
                'id' => $m->id,
                'role' => $m->role,
                'content' => $m->content,
                'created_at' => $m->created_at->toIso8601String(),
            ])
            ->toArray();
    }

    public function deleteConversation(string $ulid): bool
    {
        $conversation = AiConversation::where('ulid', $ulid)->firstOrFail();

        return $conversation->delete();
    }

    protected function getOrCreateConversation(?string $ulid, ?User $user): AiConversation
    {
        if ($ulid) {
            return AiConversation::where('ulid', $ulid)->firstOrFail();
        }

        return AiConversation::create([
            'user_id' => $user?->id,
            'ulid' => (string) Str::ulid(),
            'title' => null,
            'last_message_at' => now(),
        ]);
    }

    protected function saveUserMessage(AiConversation $conversation, string $message): AiMessage
    {
        $msg = $conversation->messages()->create([
            'role' => 'user',
            'content' => $message,
        ]);

        $conversation->update(['last_message_at' => now()]);

        return $msg;
    }

    protected function saveAssistantMessage(
        AiConversation $conversation,
        string $content,
        AiMessage $userMessage,
        array $toolCalls = []
    ): AiMessage {
        $msg = $conversation->messages()->create([
            'role' => 'assistant',
            'content' => $content,
            'tool_calls' => ! empty($toolCalls) ? $toolCalls : null,
        ]);

        // Create individual tool call records
        foreach ($toolCalls as $toolCall) {
            $msg->toolCalls()->create([
                'tool_call_id' => $toolCall['id'] ?? '',
                'tool_name' => $toolCall['name'] ?? '',
                'arguments' => $toolCall['arguments'] ?? [],
                'status' => 'success',
                'result' => null,
            ]);
        }

        $conversation->update([
            'last_message_at' => now(),
            'title' => $conversation->title ?? $this->generateTitle($userMessage->content),
        ]);

        return $msg;
    }

    protected function processResponse($response, AiConversation $conversation, AiMessage $userMessage): array
    {
        $content = $response->text ?? '';
        $toolCalls = $this->extractToolCalls($response);

        $this->saveAssistantMessage($conversation, $content, $userMessage, toolCalls: $toolCalls);

        return [
            'content' => $content,
            'conversation_id' => $conversation->ulid,
            'provider' => config('prism.default_provider'),
            'tools_used' => array_column($toolCalls, 'name'),
        ];
    }

    protected function extractToolCalls($response): array
    {
        $toolCalls = [];

        if (method_exists($response, 'steps')) {
            foreach ($response->steps as $step) {
                if (method_exists($step, 'toolCalls') && $step->toolCalls) {
                    foreach ($step->toolCalls as $toolCall) {
                        $toolCalls[] = [
                            'id' => $toolCall->id,
                            'name' => $toolCall->name,
                            'arguments' => $toolCall->arguments,
                        ];
                    }
                }
            }
        }

        return $toolCalls;
    }

    protected function generateTitle(string $firstMessage): string
    {
        return Str::limit($firstMessage, 50);
    }

    protected function handleError(\Throwable $e, AiConversation $conversation): array
    {
        return [
            'content' => 'I apologize, but I\'m having trouble connecting right now. Please try again in a moment.',
            'conversation_id' => $conversation->ulid,
            'error' => config('app.debug') ? $e->getMessage() : null,
        ];
    }
}
