<?php

namespace App\Http\Livewire;

use App\Models\Transaction;
use App\Models\User;
use App\Models\Organization;
use App\Models\Bank;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\URL;
use Livewire\Component;
use Livewire\WithPagination;
use Namu\WireChat\Models\Conversation;
use Namu\WireChat\Models\Participant;
use Namu\WireChat\Enums\ConversationType;

class Contacts extends Component
{
    use WithPagination;

    public $showSearchSection = false;
    public $search;
    public $searchInput = ''; // Temporary input for search field
    public $filterType = []; // Array of selected filter types
    public $filterTypeInput = []; // Temporary input for filter multiselect
    public $perPage = 15;
    public $sortField = 'last_interaction';
    public $sortAsc = false;

    /**
     * Sort by a specific field.
     */
    public function sortBy($field)
    {
        if ($this->sortField === $field) {
            $this->sortAsc = !$this->sortAsc;
        } else {
            $this->sortField = $field;
            $this->sortAsc = true;
        }
        $this->resetPage();
    }

    protected $rules = [
        'search' => 'nullable|string|min:2|max:100',
    ];

    protected $messages = [
        'search.min' => 'Search must be at least 2 characters.',
        'search.max' => 'Search cannot exceed 100 characters.',
    ];


    /**
     * Apply search and filter when button is clicked.
     */
    public function applySearch()
    {
        // Validate search input if provided
        if (!empty($this->searchInput) && strlen($this->searchInput) < 2) {
            $this->addError('search', 'Search must be at least 2 characters.');
            return;
        }

        if (!empty($this->searchInput) && strlen($this->searchInput) > 100) {
            $this->addError('search', 'Search cannot exceed 100 characters.');
            return;
        }

        // Apply the input values to the actual search properties
        $this->search = $this->searchInput;
        $this->filterType = $this->filterTypeInput;

        // Reset to first page when searching
        $this->resetPage();
    }

    /**
     * Get all contacts (profiles) the active profile has interacted with.
     * Includes interactions from:
     * - Laravel-love reactions (bookmarks, stars)
     * - Transactions (sent to or received from)
     * - WireChat private conversations
     *
     * @return \Illuminate\Pagination\LengthAwarePaginator
     */
    public function getContacts()
    {
        $activeProfile = getActiveProfile();

        if (!$activeProfile) {
            // Return empty paginator instead of null
            return new \Illuminate\Pagination\LengthAwarePaginator(
                collect([]),
                0,
                $this->perPage,
                1,
                ['path' => request()->url(), 'pageName' => 'page']
            );
        }

        // CRITICAL SECURITY: Validate user has ownership/access to this profile
        \App\Helpers\ProfileAuthorizationHelper::authorize($activeProfile);

        // Initialize contacts collection
        $contactsData = collect();

        // Get the reacter_id and reactant_id for the active profile
        $reacterId = $activeProfile->love_reacter_id;
        $reactantId = $activeProfile->love_reactant_id;

        // If no filters selected, show all
        $showAll = empty($this->filterType);

        // 1. Get profiles the active profile has reacted to (stars, bookmarks)
        if ($showAll || in_array('stars', $this->filterType) || in_array('bookmarks', $this->filterType)) {
            if ($reacterId) {
                $reactedProfiles = $this->getReactedProfiles($reacterId);

                // Filter by specific reaction types if selected
                if (!$showAll && (in_array('stars', $this->filterType) || in_array('bookmarks', $this->filterType))) {
                    $reactedProfiles = $reactedProfiles->filter(function ($profile) {
                        if (in_array('stars', $this->filterType) && $profile['interaction_type'] === 'star') {
                            return true;
                        }
                        if (in_array('bookmarks', $this->filterType) && $profile['interaction_type'] === 'bookmark') {
                            return true;
                        }
                        return false;
                    });
                }

                $contactsData = $contactsData->merge($reactedProfiles);
            }
        }

        // 2. Get profiles that have transacted with the active profile
        if ($showAll || in_array('transactions', $this->filterType)) {
            $transactionProfiles = $this->getTransactionProfiles($activeProfile);
            $contactsData = $contactsData->merge($transactionProfiles);
        }

        // 3. Get profiles from private WireChat conversations
        if ($showAll || in_array('conversations', $this->filterType)) {
            $conversationProfiles = $this->getConversationProfiles($activeProfile);
            $contactsData = $contactsData->merge($conversationProfiles);
        }

        // Group by profile and merge interaction data
        $contacts = $contactsData->groupBy('profile_key')->map(function ($group) {
            $first = $group->first();

            // Construct profile path like in SingleTransactionTable
            $profileTypeLower = strtolower($first['profile_type_name']);
            $profilePath = URL::to('/') . '/' . __($profileTypeLower) . '/' . $first['profile_id'];

            return [
                'profile_id' => $first['profile_id'],
                'profile_type' => $first['profile_type'],
                'profile_type_name' => $first['profile_type_name'],
                'name' => $first['name'],
                'full_name' => $first['full_name'],
                'location' => $first['location'],
                'profile_photo' => $first['profile_photo'],
                'profile_path' => $profilePath,
                'has_star' => $group->contains('interaction_type', 'star'),
                'has_bookmark' => $group->contains('interaction_type', 'bookmark'),
                'has_transaction' => $group->contains('interaction_type', 'transaction'),
                'has_conversation' => $group->contains('interaction_type', 'conversation'),
                'last_interaction' => $group->max('last_interaction'),
                'star_count' => $group->where('interaction_type', 'star')->sum('count'),
                'bookmark_count' => $group->where('interaction_type', 'bookmark')->sum('count'),
                'transaction_count' => $group->where('interaction_type', 'transaction')->sum('count'),
                'message_count' => $group->where('interaction_type', 'conversation')->sum('count'),
            ];
        })->values();

        // Apply search filter
        if (!empty($this->search) && strlen(trim($this->search)) >= 2) {
            $search = strtolower(trim($this->search));
            $contacts = $contacts->filter(function ($contact) use ($search) {
                $name = strtolower($contact['name'] ?? '');
                $fullName = strtolower($contact['full_name'] ?? '');
                $location = strtolower($contact['location'] ?? '');

                return str_contains($name, $search) ||
                       str_contains($fullName, $search) ||
                       str_contains($location, $search);
            })->values();
        }

        // Sort contacts
        $contacts = $this->sortContacts($contacts);

        // Paginate manually
        $currentPage = $this->paginators['page'] ?? 1;
        $total = $contacts->count();
        $items = $contacts->forPage($currentPage, $this->perPage);

        return new \Illuminate\Pagination\LengthAwarePaginator(
            $items,
            $total,
            $this->perPage,
            $currentPage,
            ['path' => request()->url(), 'pageName' => 'page']
        );
    }


    /**
     * Get profiles the active profile has reacted to.
     */
    private function getReactedProfiles($reacterId)
    {
        // Get all reactions by this reacter, grouped by reactant type
        $reactions = DB::table('love_reactions')
            ->join('love_reactants', 'love_reactions.reactant_id', '=', 'love_reactants.id')
            ->where('love_reactions.reacter_id', $reacterId)
            ->select(
                'love_reactants.type as reactant_type',
                DB::raw('CAST(SUBSTRING_INDEX(love_reactants.type, "\\\\", -1) AS CHAR) as reactant_model')
            )
            ->groupBy('love_reactants.type')
            ->get();

        $profiles = collect();

        foreach ($reactions as $reaction) {
            // Only process User, Organization, and Bank models
            if (!in_array($reaction->reactant_model, ['User', 'Organization', 'Bank'])) {
                continue;
            }

            $modelClass = "App\\Models\\{$reaction->reactant_model}";

            // Get all profiles of this type that were reacted to, with reaction type breakdown
            $reactedToProfiles = DB::table('love_reactions')
                ->join('love_reactants', 'love_reactions.reactant_id', '=', 'love_reactants.id')
                ->join(
                    DB::raw("(SELECT id, love_reactant_id, name,
                        full_name,
                        profile_photo_path
                        FROM " . strtolower($reaction->reactant_model) . "s) as profiles"),
                    'love_reactants.id',
                    '=',
                    'profiles.love_reactant_id'
                )
                ->where('love_reactions.reacter_id', $reacterId)
                ->where('love_reactants.type', $reaction->reactant_type)
                ->select(
                    'profiles.id as profile_id',
                    'profiles.name',
                    'profiles.full_name',
                    'profiles.profile_photo_path',
                    DB::raw("'{$modelClass}' as profile_type"),
                    DB::raw("'{$reaction->reactant_model}' as profile_type_name"),
                    'love_reactions.reaction_type_id',
                    DB::raw('MAX(love_reactions.created_at) as last_interaction'),
                    DB::raw('COUNT(*) as count')
                )
                ->groupBy('profiles.id', 'profiles.name', 'profiles.full_name', 'profiles.profile_photo_path', 'love_reactions.reaction_type_id')
                ->get();

            // Batch load locations for all profiles of this type
            $profileIds = $reactedToProfiles->pluck('profile_id');
            $locations = $this->batchLoadLocations($modelClass, $profileIds);

            foreach ($reactedToProfiles as $profile) {
                // Get location from batch-loaded data
                $location = $locations[$profile->profile_id] ?? '';

                // Determine reaction type (1 = Star, 2 = Bookmark)
                $interactionType = $profile->reaction_type_id == 1 ? 'star' : ($profile->reaction_type_id == 2 ? 'bookmark' : 'reaction');

                $profiles->push([
                    'profile_key' => $modelClass . '_' . $profile->profile_id,
                    'profile_id' => $profile->profile_id,
                    'profile_type' => $profile->profile_type,
                    'profile_type_name' => $profile->profile_type_name,
                    'name' => $profile->name,
                    'full_name' => $profile->full_name,
                    'location' => $location,
                    'profile_photo' => $profile->profile_photo_path,
                    'interaction_type' => $interactionType,
                    'last_interaction' => $profile->last_interaction,
                    'count' => $profile->count,
                ]);
            }
        }

        return $profiles;
    }


    /**
     * Get profiles that have transacted with the active profile.
     */
    private function getTransactionProfiles($activeProfile)
    {
        // Get all accounts belonging to the active profile
        $accountIds = DB::table('accounts')
            ->where('accountable_type', get_class($activeProfile))
            ->where('accountable_id', $activeProfile->id)
            ->pluck('id');

        if ($accountIds->isEmpty()) {
            return collect();
        }

        // Get all transactions involving these accounts
        $transactions = DB::table('transactions')
            ->whereIn('from_account_id', $accountIds)
            ->orWhereIn('to_account_id', $accountIds)
            ->select(
                'from_account_id',
                'to_account_id',
                DB::raw('MAX(created_at) as last_interaction'),
                DB::raw('COUNT(*) as count')
            )
            ->groupBy('from_account_id', 'to_account_id')
            ->get();

        // Group counter accounts by type for batch loading
        $counterAccountsByType = collect();

        foreach ($transactions as $transaction) {
            // Determine the counter account (the other party in the transaction)
            $counterAccountId = null;
            if ($accountIds->contains($transaction->from_account_id) && !$accountIds->contains($transaction->to_account_id)) {
                $counterAccountId = $transaction->to_account_id;
            } elseif ($accountIds->contains($transaction->to_account_id) && !$accountIds->contains($transaction->from_account_id)) {
                $counterAccountId = $transaction->from_account_id;
            }

            if ($counterAccountId) {
                $transaction->counter_account_id = $counterAccountId;
            }
        }

        // Get all counter account details in one query
        $counterAccountIds = $transactions->pluck('counter_account_id')->filter()->unique();
        $accounts = DB::table('accounts')
            ->whereIn('id', $counterAccountIds)
            ->select('id', 'accountable_type', 'accountable_id')
            ->get()
            ->keyBy('id');

        // Group profile IDs by type
        $profileIdsByType = [];
        foreach ($accounts as $account) {
            $profileTypeName = class_basename($account->accountable_type);
            if (!isset($profileIdsByType[$profileTypeName])) {
                $profileIdsByType[$profileTypeName] = [];
            }
            $profileIdsByType[$profileTypeName][] = $account->accountable_id;
        }

        // Batch load profile data and locations for each type
        $profileDataByType = [];
        $locationsByType = [];
        foreach ($profileIdsByType as $typeName => $ids) {
            $tableName = strtolower($typeName) . 's';
            $modelClass = "App\\Models\\{$typeName}";

            // Load profile data
            $profileDataByType[$typeName] = DB::table($tableName)
                ->whereIn('id', $ids)
                ->select('id', 'name', 'full_name', 'profile_photo_path')
                ->get()
                ->keyBy('id');

            // Batch load locations
            $locationsByType[$typeName] = $this->batchLoadLocations($modelClass, $ids);
        }

        // Build final profiles collection
        $profiles = collect();
        foreach ($transactions as $transaction) {
            if (!isset($transaction->counter_account_id)) {
                continue; // Skip self-transactions
            }

            $account = $accounts->get($transaction->counter_account_id);
            if (!$account) {
                continue;
            }

            $profileModel = $account->accountable_type;
            $profileId = $account->accountable_id;
            $profileTypeName = class_basename($profileModel);

            $profile = $profileDataByType[$profileTypeName][$profileId] ?? null;
            if (!$profile) {
                continue;
            }

            $location = $locationsByType[$profileTypeName][$profileId] ?? '';
            $profileKey = $profileModel . '_' . $profileId;

            $profiles->push([
                'profile_key' => $profileKey,
                'profile_id' => $profileId,
                'profile_type' => $profileModel,
                'profile_type_name' => $profileTypeName,
                'name' => $profile->name,
                'full_name' => $profile->full_name,
                'location' => $location,
                'profile_photo' => $profile->profile_photo_path,
                'interaction_type' => 'transaction',
                'last_interaction' => $transaction->last_interaction,
                'count' => $transaction->count,
            ]);
        }

        return $profiles;
    }


    /**
     * Get profiles from private WireChat conversations.
     */
    private function getConversationProfiles($activeProfile)
    {
        // Get all private conversations the active profile is participating in
        $participantType = get_class($activeProfile);
        $participantId = $activeProfile->id;

        // Get participant record for active profile
        $myParticipants = DB::table('wirechat_participants')
            ->join('wirechat_conversations', 'wirechat_participants.conversation_id', '=', 'wirechat_conversations.id')
            ->where('wirechat_participants.participantable_type', $participantType)
            ->where('wirechat_participants.participantable_id', $participantId)
            ->where('wirechat_conversations.type', ConversationType::PRIVATE->value)
            ->whereNull('wirechat_participants.deleted_at')
            ->select(
                'wirechat_participants.conversation_id',
                'wirechat_participants.last_active_at'
            )
            ->get();

        if ($myParticipants->isEmpty()) {
            return collect();
        }

        $conversationIds = $myParticipants->pluck('conversation_id');

        // Get all other participants in one query
        $otherParticipants = DB::table('wirechat_participants')
            ->whereIn('conversation_id', $conversationIds)
            ->where(function ($query) use ($participantType, $participantId) {
                $query->where('participantable_type', '!=', $participantType)
                    ->orWhere('participantable_id', '!=', $participantId);
            })
            ->whereNull('deleted_at')
            ->get()
            ->keyBy('conversation_id');

        // Get message counts for all conversations in one query
        $messageCounts = DB::table('wirechat_messages')
            ->whereIn('conversation_id', $conversationIds)
            ->whereNull('deleted_at')
            ->select(
                'conversation_id',
                DB::raw('COUNT(DISTINCT DATE(created_at)) as day_count')
            )
            ->groupBy('conversation_id')
            ->get()
            ->keyBy('conversation_id');

        // Get last messages for all conversations in one query
        $lastMessages = DB::table('wirechat_messages as wm1')
            ->whereIn('wm1.conversation_id', $conversationIds)
            ->whereNull('wm1.deleted_at')
            ->whereRaw('wm1.created_at = (SELECT MAX(wm2.created_at) FROM wirechat_messages wm2 WHERE wm2.conversation_id = wm1.conversation_id AND wm2.deleted_at IS NULL)')
            ->select('wm1.conversation_id', 'wm1.created_at')
            ->get()
            ->keyBy('conversation_id');

        // Group profile IDs by type
        $profileIdsByType = [];
        foreach ($otherParticipants as $participant) {
            $profileTypeName = class_basename($participant->participantable_type);
            if (!isset($profileIdsByType[$profileTypeName])) {
                $profileIdsByType[$profileTypeName] = [];
            }
            $profileIdsByType[$profileTypeName][] = $participant->participantable_id;
        }

        // Batch load profile data and locations for each type
        $profileDataByType = [];
        $locationsByType = [];
        foreach ($profileIdsByType as $typeName => $ids) {
            $tableName = strtolower($typeName) . 's';
            $modelClass = "App\\Models\\{$typeName}";

            // Load profile data
            $profileDataByType[$typeName] = DB::table($tableName)
                ->whereIn('id', $ids)
                ->select('id', 'name', 'full_name', 'profile_photo_path')
                ->get()
                ->keyBy('id');

            // Batch load locations
            $locationsByType[$typeName] = $this->batchLoadLocations($modelClass, $ids);
        }

        // Build final profiles collection
        $profiles = collect();
        foreach ($myParticipants as $myParticipant) {
            $otherParticipant = $otherParticipants->get($myParticipant->conversation_id);
            if (!$otherParticipant) {
                continue;
            }

            $profileModel = $otherParticipant->participantable_type;
            $profileId = $otherParticipant->participantable_id;
            $profileTypeName = class_basename($profileModel);

            $profile = $profileDataByType[$profileTypeName][$profileId] ?? null;
            if (!$profile) {
                continue;
            }

            $messageCount = $messageCounts->get($myParticipant->conversation_id)->day_count ?? 0;
            $lastMessage = $lastMessages->get($myParticipant->conversation_id);
            $location = $locationsByType[$profileTypeName][$profileId] ?? '';
            $profileKey = $profileModel . '_' . $profileId;

            $profiles->push([
                'profile_key' => $profileKey,
                'profile_id' => $profileId,
                'profile_type' => $profileModel,
                'profile_type_name' => $profileTypeName,
                'name' => $profile->name,
                'full_name' => $profile->full_name,
                'location' => $location,
                'profile_photo' => $profile->profile_photo_path,
                'interaction_type' => 'conversation',
                'last_interaction' => $lastMessage ? $lastMessage->created_at : $myParticipant->last_active_at,
                'count' => $messageCount,
            ]);
        }

        return $profiles;
    }


    /**
     * Batch load locations for multiple profiles of the same type.
     * This replaces the N+1 query problem in getProfileLocation().
     *
     * @param string $modelClass The model class (e.g., 'App\Models\User')
     * @param array|\Illuminate\Support\Collection $profileIds Array of profile IDs
     * @return array Associative array of profile_id => location_name
     */
    private function batchLoadLocations($modelClass, $profileIds)
    {
        if (empty($profileIds)) {
            return [];
        }

        // Ensure it's an array
        if ($profileIds instanceof \Illuminate\Support\Collection) {
            $profileIds = $profileIds->toArray();
        }

        // Load all profiles with their location relationships
        $profiles = $modelClass::with([
            'locations.city.translations',
            'locations.district.translations',
            'locations.division.translations',
            'locations.country.translations'
        ])
        ->whereIn('id', $profileIds)
        ->get();

        // Build location map
        $locationMap = [];
        foreach ($profiles as $profile) {
            if (method_exists($profile, 'getLocationFirst')) {
                $locationData = $profile->getLocationFirst(false);
                $locationMap[$profile->id] = $locationData['name'] ?? $locationData['name_short'] ?? '';
            } else {
                $locationMap[$profile->id] = '';
            }
        }

        return $locationMap;
    }

    /**
     * Get location for a profile (deprecated, use batchLoadLocations instead).
     * Kept for backwards compatibility.
     */
    private function getProfileLocation($modelClass, $profileId)
    {
        $locations = $this->batchLoadLocations($modelClass, [$profileId]);
        return $locations[$profileId] ?? '';
    }


    /**
     * Sort contacts based on sort field and direction.
     */
    private function sortContacts($contacts)
    {
        $sortField = $this->sortField;
        $sortAsc = $this->sortAsc;

        return $contacts->sort(function ($a, $b) use ($sortField, $sortAsc) {
            $aVal = $a[$sortField] ?? '';
            $bVal = $b[$sortField] ?? '';

            if ($sortField === 'last_interaction') {
                $comparison = strtotime($bVal) <=> strtotime($aVal); // Default: most recent first
                return $sortAsc ? -$comparison : $comparison;
            }

            // For count fields, use numeric comparison
            if (in_array($sortField, ['transaction_count', 'message_count'])) {
                $comparison = ($aVal ?? 0) <=> ($bVal ?? 0);
                return $sortAsc ? $comparison : -$comparison;
            }

            // For boolean fields
            if (in_array($sortField, ['has_star', 'has_bookmark'])) {
                $comparison = ($aVal ? 1 : 0) <=> ($bVal ? 1 : 0);
                return $sortAsc ? $comparison : -$comparison;
            }

            // String comparison for name, location, etc.
            $comparison = strcasecmp($aVal, $bVal);
            return $sortAsc ? $comparison : -$comparison;
        })->values();
    }


    /**
     * Reset search and filters.
     */
    public function resetSearch()
    {
        $this->resetPage();
        $this->showSearchSection = false;
        $this->search = null;
        $this->searchInput = '';
        $this->filterType = [];
        $this->filterTypeInput = [];
    }


    /**
     * Scroll to top when page changes.
     */
    public function updatedPage()
    {
        $this->dispatch('scroll-to-top');
    }


    public function render()
    {
        return view('livewire.contacts.show', [
            'contacts' => $this->getContacts(),
        ]);
    }
}
