<?php

namespace App\Services;

use App\Models\ChatCategory;
use App\Models\ChatConversation;
use App\Models\ChatMessage;
use App\Models\ChatAgent;
use App\Models\User;
use App\Exceptions\ChatException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Collection;

/**
 * ChatService
 * 
 * Handles chat-related business logic for the Communication Module.
 * Manages agents, categories, conversations, and messages.
 * 
 * Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 8.3
 */
class ChatService
{
    /**
     * Start a new conversation with a visitor.
     *
     * @param array $visitorData (session_id, name, email, phone, ip, user_agent)
     * @param int|null $categoryId
     * @param string|null $initialMessage
     * @return ChatConversation
     */
    public function startConversation(array $visitorData, ?int $categoryId = null, ?string $initialMessage = null): ChatConversation
    {
        return DB::transaction(function () use ($visitorData, $categoryId, $initialMessage) {
            // Check for existing conversation with this session
            $existingConversation = ChatConversation::where('session_id', $visitorData['session_id'])
                ->open()
                ->first();

            if ($existingConversation) {
                return $existingConversation;
            }

            // Create new conversation
            $conversation = ChatConversation::create([
                'session_id' => $visitorData['session_id'],
                'category_id' => $categoryId,
                'visitor_name' => $visitorData['name'] ?? null,
                'visitor_email' => $visitorData['email'] ?? null,
                'visitor_phone' => $visitorData['phone'] ?? null,
                'visitor_ip' => $visitorData['ip'] ?? null,
                'user_agent' => $visitorData['user_agent'] ?? null,
                'status' => ChatConversation::STATUS_WAITING,
                'initial_message' => $initialMessage,
                'started_at' => now(),
                'last_message_at' => now(),
            ]);

            // Add initial message if provided
            if ($initialMessage) {
                $this->addVisitorMessage($conversation, $initialMessage, $visitorData['name'] ?? null);
            }

            // Auto-assign agent if available
            $this->autoAssignAgent($conversation);

            // Add system message
            ChatMessage::createSystemMessage(
                $conversation->id,
                ChatMessage::EVENT_CONVERSATION_STARTED,
                'Conversation started',
                ['visitor_name' => $visitorData['name'] ?? 'Guest']
            );

            return $conversation;
        });
    }

    /**
     * Auto-assign an available agent to a conversation.
     *
     * @param ChatConversation $conversation
     * @return bool
     */
    public function autoAssignAgent(ChatConversation $conversation): bool
    {
        // Find best available agent
        $agent = ChatAgent::findBestAvailable($conversation->category_id);

        if (!$agent) {
            // No agent available, keep in waiting
            return false;
        }

        $this->assignAgent($conversation, $agent->user_id);

        return true;
    }

    /**
     * Assign a specific agent to a conversation.
     *
     * @param ChatConversation $conversation
     * @param int $agentId User ID of the agent
     * @return ChatConversation
     */
    public function assignAgent(ChatConversation $conversation, int $agentId): ChatConversation
    {
        $agent = User::findOrFail($agentId);

        // Verify user is a chat agent
        $chatAgent = ChatAgent::where('user_id', $agentId)->active()->first();

        if (!$chatAgent) {
            throw new ChatException('User is not an active chat agent.');
        }

        return DB::transaction(function () use ($conversation, $agentId, $agent, $chatAgent) {
            // Assign to conversation
            $conversation->assignAgent($agentId);

            // Update agent stats
            $chatAgent->incrementChatCount();

            // Add system message
            ChatMessage::createSystemMessage(
                $conversation->id,
                ChatMessage::EVENT_AGENT_JOINED,
                $agent->name . ' joined the conversation',
                ['agent_id' => $agentId, 'agent_name' => $agent->name]
            );

            return $conversation->fresh();
        });
    }

    /**
     * Transfer conversation to another agent.
     *
     * @param ChatConversation $conversation
     * @param int $newAgentId
     * @param int $transferredBy User who initiated transfer
     * @param string|null $reason
     * @return ChatConversation
     */
    public function transferConversation(ChatConversation $conversation, int $newAgentId, int $transferredBy, ?string $reason = null): ChatConversation
    {
        $oldAgentId = $conversation->agent_id;
        $oldAgent = User::find($oldAgentId);

        return DB::transaction(function () use ($conversation, $newAgentId, $transferredBy, $reason, $oldAgent, $oldAgentId) {
            // Update old agent's chat count safely
            if ($oldAgentId) {
                $oldAgentModel = ChatAgent::where('user_id', $oldAgentId)->first();
                if ($oldAgentModel) {
                    $oldAgentModel->decrementChatCount();
                }
            }

            // Assign to new agent
            $conversation->assignAgent($newAgentId);

            // Update new agent's chat count
            $newAgentModel = ChatAgent::where('user_id', $newAgentId)->first();
            if ($newAgentModel) {
                $newAgentModel->incrementChatCount();
            }

            // Add system message
            ChatMessage::createSystemMessage(
                $conversation->id,
                ChatMessage::EVENT_AGENT_TRANSFERRED,
                'Conversation transferred',
                [
                    'from_agent' => $oldAgent?->name,
                    'reason' => $reason,
                    'transferred_by' => User::find($transferredBy)?->name,
                ]
            );

            return $conversation->fresh();
        });
    }

    /**
     * Add a message from a visitor.
     *
     * @param ChatConversation $conversation
     * @param string $message
     * @param string|null $visitorName
     * @return ChatMessage
     */
    public function addVisitorMessage(ChatConversation $conversation, string $message, ?string $visitorName = null): ChatMessage
    {
        return DB::transaction(function () use ($conversation, $message, $visitorName) {
            $chatMessage = ChatMessage::createVisitorMessage(
                $conversation->id,
                $message,
                $visitorName ?? $conversation->visitor_name
            );

            $conversation->incrementMessageCount(false);

            return $chatMessage;
        });
    }

    /**
     * Add a message from an agent.
     *
     * @param ChatConversation $conversation
     * @param int $agentId
     * @param string $message
     * @param bool $isInternal
     * @return ChatMessage
     */
    public function addAgentMessage(ChatConversation $conversation, int $agentId, string $message, bool $isInternal = false): ChatMessage
    {
        return DB::transaction(function () use ($conversation, $agentId, $message, $isInternal) {
            $chatMessage = ChatMessage::createAgentMessage(
                $conversation->id,
                $agentId,
                $message,
                $isInternal
            );

            $conversation->incrementMessageCount(true);

            // Update agent's last activity
            ChatAgent::where('user_id', $agentId)->update(['last_active_at' => now()]);

            return $chatMessage;
        });
    }

    /**
     * End a conversation.
     *
     * @param ChatConversation $conversation
     * @param int|null $endedBy User ID who ended it
     * @param string $reason
     * @param string|null $notes
     * @return ChatConversation
     */
    public function endConversation(ChatConversation $conversation, ?int $endedBy = null, string $reason = ChatConversation::END_REASON_AGENT_CLOSED, ?string $notes = null): ChatConversation
    {
        return DB::transaction(function () use ($conversation, $endedBy, $reason, $notes) {
            // Update agent's chat count safely
            if ($conversation->agent_id) {
                $agentModel = ChatAgent::where('user_id', $conversation->agent_id)->first();
                if ($agentModel) {
                    $agentModel->decrementChatCount();
                }
            }

            // Close the conversation
            $conversation->close($endedBy, $reason, $notes);

            // Add system message
            ChatMessage::createSystemMessage(
                $conversation->id,
                ChatMessage::EVENT_CONVERSATION_ENDED,
                'Conversation ended',
                ['reason' => $reason, 'ended_by' => $endedBy ? User::find($endedBy)?->name : 'System']
            );

            return $conversation->fresh();
        });
    }

    /**
     * Record visitor satisfaction rating.
     *
     * @param ChatConversation $conversation
     * @param int $rating 1-5
     * @param string|null $comment
     * @return ChatConversation
     */
    public function recordSatisfaction(ChatConversation $conversation, int $rating, ?string $comment = null): ChatConversation
    {
        $conversation->recordSatisfaction($rating, $comment);

        // Update agent's average rating
        if ($conversation->agent_id) {
            $agent = ChatAgent::where('user_id', $conversation->agent_id)->first();
            $agent?->updateSatisfactionRating($rating);
        }

        return $conversation->fresh();
    }

    /**
     * Get open conversations for an agent.
     *
     * @param int $agentId
     * @return Collection
     */
    public function getAgentConversations(int $agentId): Collection
    {
        return ChatConversation::assignedTo($agentId)
            ->open()
            ->with(['category', 'messages' => function ($q) {
                $q->latest()->limit(50);
            }])
            ->orderBy('priority', 'desc')
            ->orderBy('last_message_at', 'asc')
            ->get();
    }

    /**
     * Get waiting conversations (queue).
     *
     * @param int|null $categoryId
     * @return Collection
     */
    public function getWaitingConversations(?int $categoryId = null): Collection
    {
        $query = ChatConversation::waiting()
            ->with(['category'])
            ->orderBy('priority', 'desc')
            ->orderBy('created_at', 'asc');

        if ($categoryId) {
            $query->where('category_id', $categoryId);
        }

        return $query->get();
    }

    /**
     * Get conversation history with filters.
     *
     * @param array $filters
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
     */
    public function getConversationHistory(array $filters = [])
    {
        $query = ChatConversation::with(['category', 'agent', 'messages']);

        if (!empty($filters['status'])) {
            $query->where('status', $filters['status']);
        }

        if (!empty($filters['agent_id'])) {
            $query->where('agent_id', $filters['agent_id']);
        }

        if (!empty($filters['category_id'])) {
            $query->where('category_id', $filters['category_id']);
        }

        if (!empty($filters['date_from'])) {
            $query->whereDate('started_at', '>=', $filters['date_from']);
        }

        if (!empty($filters['date_to'])) {
            $query->whereDate('started_at', '<=', $filters['date_to']);
        }

        if (!empty($filters['search'])) {
            $query->where(function ($q) use ($filters) {
                $q->where('visitor_name', 'like', "%{$filters['search']}%")
                  ->orWhere('visitor_email', 'like', "%{$filters['search']}%")
                  ->orWhereHas('messages', function ($mq) use ($filters) {
                      $mq->where('message', 'like', "%{$filters['search']}%");
                  });
            });
        }

        $sortBy = $filters['sort_by'] ?? 'started_at';
        $sortOrder = $filters['sort_order'] ?? 'desc';

        return $query->orderBy($sortBy, $sortOrder)
            ->paginate($filters['per_page'] ?? 15);
    }

    /**
     * Get chat statistics.
     *
     * @return array
     */
    public function getStatistics(): array
    {
        $total = ChatConversation::count();

        return [
            'total' => $total,
            'total_conversations' => $total,
            'active' => ChatConversation::active()->count(),
            'waiting' => ChatConversation::waiting()->count(),
            'resolved' => ChatConversation::resolved()->count(),
            'missed' => ChatConversation::missed()->count(),
            'agents_online' => ChatAgent::getOnlineCount(),
            'online_agents' => ChatAgent::getOnlineCount(),
            'available_agents' => ChatAgent::getAvailableCount(),
            'avg_wait_time' => ChatConversation::getAverageWaitTime(),
            'avg_duration' => ChatConversation::getAverageDuration(),
            'avg_satisfaction' => ChatConversation::getAverageSatisfaction(),
            'today' => ChatConversation::whereDate('started_at', today())->count(),
            'this_week' => ChatConversation::whereBetween('started_at', [now()->startOfWeek(), now()->endOfWeek()])->count(),
        ];
    }

    /**
     * Appoint a user as chat agent.
     *
     * @param int $userId User to appoint
     * @param int $appointedBy User making the appointment
     * @param array $options Additional options (categories, max_chats, schedule)
     * @return ChatAgent
     */
    public function appointAgent(int $userId, int $appointedBy, array $options = []): ChatAgent
    {
        $user = User::findOrFail($userId);
        $appointer = User::findOrFail($appointedBy);

        // Verify appointer is Super Manager or Manager
        if (!$appointer->hasAnyRole(['Super Manager', 'Manager'])) {
            throw new ChatException('Only administrators can appoint chat agents.');
        }

        // Check if already an agent
        $existingAgent = ChatAgent::where('user_id', $userId)->first();

        if ($existingAgent) {
            // Update existing agent
            $existingAgent->update([
                'is_active' => true,
                'categories' => $options['categories'] ?? null,
                'max_concurrent_chats' => $options['max_chats'] ?? 3,
                'availability_schedule' => $options['schedule'] ?? null,
            ]);

            return $existingAgent;
        }

        // Create new agent
        return ChatAgent::create([
            'user_id' => $userId,
            'appointed_by' => $appointedBy,
            'categories' => $options['categories'] ?? null,
            'max_concurrent_chats' => $options['max_chats'] ?? 3,
            'availability_schedule' => $options['schedule'] ?? null,
            'status' => ChatAgent::STATUS_OFFLINE,
            'is_active' => true,
        ]);
    }

    /**
     * Remove chat agent.
     *
     * @param int $agentId ChatAgent ID
     * @param int $removedBy User removing the agent
     * @return bool
     */
    public function removeAgent(int $agentId, int $removedBy): bool
    {
        $remover = User::findOrFail($removedBy);

        if (!$remover->hasAnyRole(['Super Manager', 'Manager'])) {
            throw new ChatException('Only administrators can remove chat agents.');
        }

        $agent = ChatAgent::findOrFail($agentId);

        // Deactivate instead of delete to preserve history
        $agent->deactivate();

        return true;
    }

    /**
     * Update agent status.
     *
     * @param int $userId Agent user ID
     * @param string $status
     * @return ChatAgent
     */
    public function updateAgentStatus(int $userId, string $status): ChatAgent
    {
        $agent = ChatAgent::where('user_id', $userId)->firstOrFail();

        switch ($status) {
            case ChatAgent::STATUS_ONLINE:
                $agent->setOnline();
                break;
            case ChatAgent::STATUS_AWAY:
                $agent->setAway();
                break;
            case ChatAgent::STATUS_OFFLINE:
                $agent->setOffline();
                break;
            case ChatAgent::STATUS_BUSY:
                $agent->setBusy();
                break;
            default:
                throw new ChatException('Invalid status.');
        }

        return $agent->fresh();
    }

    /**
     * Get all chat agents with filters.
     *
     * @param array $filters
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
     */
    public function getAgents(array $filters = [])
    {
        $query = ChatAgent::with(['user', 'creator']);

        if (!empty($filters['status'])) {
            $query->where('status', $filters['status']);
        }

        if (isset($filters['is_active'])) {
            $query->where('is_active', $filters['is_active']);
        }

        if (!empty($filters['search'])) {
            $query->whereHas('user', function ($q) use ($filters) {
                $q->where('name', 'like', "%{$filters['search']}%")
                  ->orWhere('email', 'like', "%{$filters['search']}%");
            });
        }

        return $query->orderBy('created_at', 'desc')
            ->paginate($filters['per_page'] ?? 15);
    }

    /**
     * Create a chat category.
     *
     * @param array $data Category data
     * @param int $createdBy User creating the category
     * @return ChatCategory
     */
    public function createCategory(array $data, int $createdBy): ChatCategory
    {
        $user = User::findOrFail($createdBy);

        if (!$user->hasAnyRole(['Super Manager', 'Manager'])) {
            throw new ChatException('Only administrators can create chat categories.');
        }

        $data['slug'] = ChatCategory::generateSlug($data['name']);
        $data['created_by'] = $createdBy;

        return ChatCategory::create($data);
    }

    /**
     * Update a chat category.
     *
     * @param int $categoryId
     * @param array $data
     * @param int $updatedBy
     * @return ChatCategory
     */
    public function updateCategory(int $categoryId, array $data, int $updatedBy): ChatCategory
    {
        $user = User::findOrFail($updatedBy);

        if (!$user->hasAnyRole(['Super Manager', 'Manager'])) {
            throw new ChatException('Only administrators can update chat categories.');
        }

        $category = ChatCategory::findOrFail($categoryId);

        if (!empty($data['name']) && $data['name'] !== $category->name) {
            $data['slug'] = ChatCategory::generateSlug($data['name']);
        }

        $category->update($data);

        return $category->fresh();
    }

    /**
     * Delete a chat category.
     *
     * @param int $categoryId
     * @param int $deletedBy
     * @return bool
     */
    public function deleteCategory(int $categoryId, int $deletedBy): bool
    {
        $user = User::findOrFail($deletedBy);

        if (!$user->hasAnyRole(['Super Manager', 'Manager'])) {
            throw new ChatException('Only administrators can delete chat categories.');
        }

        $category = ChatCategory::findOrFail($categoryId);

        // Check if category has conversations
        if ($category->conversations()->exists()) {
            throw new ChatException('Cannot delete category with existing conversations.');
        }

        return $category->delete();
    }

    /**
     * Get all categories.
     *
     * @param bool $activeOnly
     * @return Collection
     */
    public function getCategories(bool $activeOnly = false): Collection
    {
        $query = ChatCategory::withCount('conversations');

        if ($activeOnly) {
            $query->active();
        }

        return $query->ordered()->get();
    }

    /**
     * Mark messages as read.
     *
     * @param ChatConversation $conversation
     * @param string $readerType 'agent' or 'visitor'
     * @return int Number of messages marked as read
     */
    public function markMessagesAsRead(ChatConversation $conversation, string $readerType): int
    {
        // Get unread messages not sent by the reader
        $query = ChatMessage::where('conversation_id', $conversation->id)
            ->where('is_read', false)
            ->where('sender_type', '!=', $readerType);

        $count = $query->count();

        $query->update([
            'is_read' => true,
            'read_at' => now(),
        ]);

        return $count;
    }

    /**
     * Get unread message count for a conversation.
     *
     * @param int $conversationId
     * @param string $readerType
     * @return int
     */
    public function getUnreadCount(int $conversationId, string $readerType): int
    {
        return ChatMessage::where('conversation_id', $conversationId)
            ->where('is_read', false)
            ->where('sender_type', '!=', $readerType)
            ->count();
    }

    /**
     * Handle agent timeout (auto-set to away/offline).
     *
     * @param int $timeoutMinutes Minutes of inactivity before timeout
     * @return int Number of agents timed out
     */
    public function handleAgentTimeout(int $timeoutMinutes = 15): int
    {
        $timeoutThreshold = now()->subMinutes($timeoutMinutes);

        $agents = ChatAgent::where('status', ChatAgent::STATUS_ONLINE)
            ->where('last_active_at', '<', $timeoutThreshold)
            ->get();

        foreach ($agents as $agent) {
            $agent->setAway();
        }

        return $agents->count();
    }

    /**
     * Handle missed conversations (no agent picked up within threshold).
     *
     * @param int $timeoutMinutes Minutes before conversation is marked as missed
     * @return int Number of conversations marked as missed
     */
    public function handleMissedConversations(int $timeoutMinutes = 30): int
    {
        $timeoutThreshold = now()->subMinutes($timeoutMinutes);

        $conversations = ChatConversation::waiting()
            ->where('created_at', '<', $timeoutThreshold)
            ->get();

        foreach ($conversations as $conversation) {
            $conversation->markAsMissed();

            // Add system message
            ChatMessage::createSystemMessage(
                $conversation->id,
                ChatMessage::EVENT_CONVERSATION_ENDED,
                'Conversation missed - no agent available',
                ['reason' => 'timeout']
            );
        }

        return $conversations->count();
    }
}
