<?php

namespace App\Http\Livewire\Tags;

use App\Http\Livewire\Traits\RequiresAdminAuthorization;
use App\Models\Bank;
use App\Models\Category;
use App\Models\CategoryTranslation;
use App\Models\Language;
use App\Models\Organization;
use App\Models\Tag;
use App\Models\TaggableLocale;
use App\Models\TaggableLocaleContext;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
use Livewire\WithPagination;
use WireUi\Traits\WireUiActions;

class Manage extends Component
{
    use WithPagination;
    use WireUiActions;
    use RequiresAdminAuthorization;

    public string $search = '';
    public bool $showModal = false;
    public $createTranslation;
    public int $tagId;
    public $bulkSelected = [];
    public string $confirmString;
    public int $categoryId;
    public int $tag;

    public bool $modalDeleteTag = false;
    public bool $modalBulkDeleteTags = false;
    public bool $modalEditTag = false;
    public $selectedTag;
    public $selectedTags;
    public int $selectedTagId;    // Needed for the modalDeleteTag
    public string $newCategoryColor;
    public int $countTotal;
    public int $countTotalTranslations;

    public $localeInit;
    public $locale;
    public $localesOptions = [];
    public $language;

    public $initTag = [];
    public $editTag = [
        'name' => '',
        'language' => '',
        'comment' => '',
        'category' => null,
        'mergeTag' => null,
        ];
    public bool $editTagChanged = false;
    public bool $editTagLanguageChanged = false;
    public bool $editTagContextChanged = false;
    public $categoryOptions = [];
    public $mergeTagOptions = [];
    public string $mergeName;
    public string $mergeCategoryColor;

    public string $sortField = 'updated_at'; // Default sort field
    public string $sortDirection = 'desc'; // Default sort direction
    public $perPage = 10; // default 10 results per page
    public $page;

    protected $listeners = [
        'tagCreated' => '$refresh', // $refresh is a magic method that forces a re-render
    ];

    public function mount()
    {
        // Admin Authorization - Prevent IDOR attacks and cross-guard access
        $activeProfileType = session('activeProfileType');
        $activeProfileId = session('activeProfileId');

        if (!$activeProfileType || !$activeProfileId) {
            abort(403, __('No active profile selected'));
        }

        $profile = $activeProfileType::find($activeProfileId);

        if (!$profile) {
            abort(403, __('Active profile not found'));
        }

        // Validate profile ownership using ProfileAuthorizationHelper (prevents cross-guard attacks)
        \App\Helpers\ProfileAuthorizationHelper::authorize($profile);

        // Verify admin or central bank permissions
        if ($profile instanceof \App\Models\Admin) {
            // Admin access OK
        } elseif ($profile instanceof \App\Models\Bank) {
            // Only central bank (level 0) can access tag management
            if ($profile->level !== 0) {
                abort(403, __('Central bank access required for tag management'));
            }
        } else {
            abort(403, __('Admin or central bank access required'));
        }

        // Log admin access for security monitoring
        \Log::info('Tags management access', [
            'component' => 'Tags\\Manage',
            'profile_id' => $profile->id,
            'profile_type' => get_class($profile),
            'authenticated_guard' => \Auth::getDefaultDriver(),
            'ip_address' => request()->ip(),
        ]);
    }

    protected function rules()
    {
        return [
        'editTag.name' => array_merge(
            timebank_config('tags.name_rule'),
            [
                // Check if the name exist in the selected locale if the locale was changed
                function ($attribute, $value, $fail) {
                    if ($this->editTagLanguageChanged) {
                        $existsInSelectedLanguage = DB::table('taggable_tags')
                            ->join('taggable_locales', 'taggable_tags.tag_id', '=', 'taggable_locales.taggable_tag_id')
                            ->where('taggable_locales.locale', $this->editTag['language'])
                            ->where(function ($query) use ($value) {
                                $query->where('taggable_tags.name', $value)
                                    ->orWhere('taggable_tags.normalized', $value);
                            })
                            ->exists();

                        if ($existsInSelectedLanguage) {
                            $fail(__('This :attribute name already exists.'));
                        }
                    }
                },
            ]
        ),
        'editTag.comment' => timebank_config('tags.comment_rule'),
        'editTag.category' => 'required|int',
        'editTag.mergeTag' => 'sometimes',
            'confirmString' => [
                'required',
                function ($attribute, $value, $fail) {
                    // For both single and bulk delete validation
                    $expected = __('messages.confirm_input_string');
                    if ($value !== $expected) {
                        $fail(__('The confirmation keyword is incorrect.'));
                    }
                },
            ],
        ];
    }

    protected $queryString = [
        'search' => ['except' => ''],
        'sortField' => ['except' => 'updated_at'],
        'sortDirection' => ['except' => 'desc'],
        'perPage' => ['except' => 10],
        'page' => ['except' => 1]
    ];

    public function openDeleteTagModal($tagId)
    {
        $this->confirmString = '';
        $countUsers = Tag::find($tagId)->users->count();
        $countOrgs = Tag::find($tagId)->organizations->count();
        $countBanks = Tag::find($tagId)->banks->count();
        $this->countTotal = $countUsers + $countOrgs + $countBanks;
        $this->selectedTag = Tag::find($tagId);
        $this->selectedTagId = $tagId;
        $this->modalDeleteTag = true;
    }


    public function openBulkDeleteTagsModal()
    {
        // \Log::debug('Opening bulk delete modal', [
        //     'bulkSelected' => $this->bulkSelected,
        //     'current_page' => request()->get('page', 1)
        // ]);

        $this->confirmString = '';

        // Validate that selected tags actually exist on current page or in database
        $validSelectedTags = Tag::whereIn('tag_id', $this->bulkSelected)->pluck('tag_id')->toArray();

        if (empty($validSelectedTags)) {
            $this->notification()->error(
                $title = __('Error'),
                $description = __('No valid tags selected for deletion.')
            );
            $this->resetBulkSelection();
            return;
        }

        $this->bulkSelected = $validSelectedTags; // Clean up invalid selections

        // Continue with existing logic...
        $selectedTags = Tag::whereIn('tag_id', $this->bulkSelected)
            ->withCount(['users', 'organizations', 'banks'])
            ->get();

        $uniqueUsersCount = DB::table('taggable_taggables')
            ->whereIn('tag_id', $this->bulkSelected)
            ->where('taggable_type', User::class)
            ->distinct()
            ->count('taggable_id');

        $uniqueOrgsCount = DB::table('taggable_taggables')
            ->whereIn('tag_id', $this->bulkSelected)
            ->where('taggable_type', Organization::class)
            ->distinct()
            ->count('taggable_id');

        $uniqueBanksCount = DB::table('taggable_taggables')
            ->whereIn('tag_id', $this->bulkSelected)
            ->where('taggable_type', Bank::class)
            ->distinct()
            ->count('taggable_id');

        $this->countTotal = $uniqueUsersCount + $uniqueOrgsCount + $uniqueBanksCount;

        // Convert the result to proper Tag models
        $this->selectedTags = $selectedTags;

        $this->modalBulkDeleteTags = true;
        $this->selectedTag = null; // make sure edit tag modal does not activate
    }


    public function openEditTagModal($tagId)
    {
        $this->confirmString = '';
        // Count profiles who are associated with this tag
        $tag = Tag::withCount(['users', 'organizations', 'banks'])->find($tagId);
        $this->countTotal = $tag->users_count + $tag->organizations_count + $tag->banks_count;

        // Count profiles who are associated with the context of this tag, so also who have a translation of this tag associated
        $contextId = TaggableLocaleContext::where('tag_id', $tagId)->value('context_id');
        $tagsInContext = TaggableLocaleContext::where('context_id', $contextId)->pluck('tag_id')->values();
        $tags = Tag::whereIn('tag_id', $tagsInContext)
            ->withCount(['users', 'organizations', 'banks'])
            ->get();
        $countTagsInContextUsers = $tags->sum('users_count');
        $countTagsInContextOrgs = $tags->sum('organizations_count');
        $countTagsInContextBanks = $tags->sum('banks_count');
        $this->countTotalTranslations = $countTagsInContextUsers + $countTagsInContextOrgs + $countTagsInContextBanks - $this->countTotal;

        $this->selectedTag = $tag;
        $this->selectedTagId = $tagId;

        // Get categories to select from
        $categoryPath = (new \App\Models\Tag())->translateTagIdWithContext($this->selectedTag->tag_id)['category_id'] ?? '';

        $this->categoryOptions = Category::where('type', Tag::class)
            ->get()
            ->map(function ($category) {
                // Include all attributes, including appended ones
                return [
                    'category_id' => $category->id,
                    'name' => ucfirst($category->translation->name ?? ''), // Use the appended 'translation' attribute
                    'description' => $category->relatedPathExSelfTranslation ?? '', // Appended attribute
                    'color' => $category->relatedColor ?? 'gray',
                ];
            })
            ->sortBy('name')
            ->values();


        // Get languages to select from: exclude all locales that already exists for this tag. But include the selected locale.
        $languages = Language::all();
        $selectedLocale = TaggableLocale::where('taggable_tag_id', $tagId)->first()->locale;
        $optionLocales = array_diff($tag->locales->pluck('locale')->toArray(), [$selectedLocale]);
        $this->localesOptions = $languages->filter(function ($lang) use ($optionLocales) {
            return !in_array($lang->lang_code, $optionLocales);
        })->map(function ($lang) {
            return [
                'lang_code' => $lang->lang_code,
                'label'     => $lang->flag . ' ' . trans('messages.' . $lang->name),
            ];
        })->toArray();


        // Get tags to choose for merging the tag
        $this->mergeTagOptions = $this->getMergeTagOptions($selectedLocale);

        $this->initTag = [
            'name' => $this->selectedTag->name,
            'language' => $this->selectedTag->locale->locale,
            'comment' => $this->selectedTag->locale->comment,
            'category' => $categoryPath,
            ];
        $this->editTag = $this->initTag;

        $this->modalEditTag = true;
    }


    public function getMergeTagOptions($locale)
    {
        $options = Tag::whereHas('locale', function ($query) use ($locale) {
            $query->where('locale', $locale);
        })
            ->where('tag_id', '<>', $this->selectedTagId) // exclude the initial tag
            ->get(['name', 'tag_id'])
            ->map(function ($tag) {
                return [
                    'name'   => $tag->tag_id . '&nbsp;&nbsp;&nbsp;&nbsp;' . $tag->name,
                    'tag_id' => $tag->tag_id,
                ];
            })
            ->toArray();

        return $options;
    }


    /**
     * Recalculate the overall changed state for the tag.
     * It sets $this->editTagChanged to true if ANY of the relevant fields differ from $this->initTag.
     */
    protected function syncEditTagChangedState(): void
    {
        $keysToCheck = [
            'name',
            'language',
            'category'];

        $changed = false;
        foreach ($keysToCheck as $key) {
            $current = $this->editTag[$key] ?? null;
            $initial = $this->initTag[$key] ?? null;
            if ($current !== $initial) {
                $changed = true;
                break;
            }
        }
        $this->editTagChanged = $changed;
    }


    public function updatedEditTag($newValue, $fieldKey)
    {
        // Handle specific field updates

        // After handling any field-specific logic, recalc the overall changed flag.
        $this->syncEditTagChangedState();
    }



    /**
     * Delete the tag
     *
     * @param  mixed $translationId
     * @return void
     */
    public function deleteTag()
    {
        // CRITICAL: Authorize admin access for deleting tag
        $this->authorizeAdminAccess();

        $this->validateOnly('confirmString');
        try {
            $tag = Tag::find($this->selectedTagId);
            if ($tag) {
                Tag::whereIn('tag_id', [$this->selectedTagId])->delete();
                $this->resetForm();
                $this->resetPage();
                $this->dispatch('scroll-to-top'); // Add this line

                $this->notification()->success(
                    $title = __('Deleted'),
                    $description = __('Tag was deleted successfully!')
                );
            } else {
                $this->notification()->error(
                    $title = __('Error!'),
                    $description = __('Tag not found.')
                );
            }
        } catch (\Exception $e) {
            $this->notification()->error(
                $title = __('Error!'),
                $description = __('Oops, could not delete the tag!') . $e->getMessage()
            );
        }
        $this->resetPage();
        $this->dispatch('scroll-to-top'); // Add this line
        $this->modalDeleteTag = false;
    }


    public function deleteSelected()
    {
        // CRITICAL: Authorize admin access for bulk deleting tags
        $this->authorizeAdminAccess();

        // Validate the confirmation string
        $this->validate(['confirmString' => $this->rules()['confirmString']]);

        try {
            $tags = Tag::whereIn('tag_id', $this->bulkSelected);
            if ($tags->count() > 0) {
                $tags->delete();
                $this->notification()->success(
                    $title = __('Deleted'),
                    $description = __('Selected tags were deleted successfully!')
                );

                // Reset and close modal only on success
                $this->resetPage();
                $this->dispatch('scroll-to-top');
                $this->bulkSelected = [];
                $this->confirmString = '';
                $this->modalBulkDeleteTags = false;
            } else {
                $this->notification()->error(
                    $title = __('Error!'),
                    $description = __('The selected tags were not found.')
                );
            }
        } catch (\Illuminate\Validation\ValidationException $e) {
            // Re-throw validation errors so they display in the modal
            throw $e;
        } catch (\Exception $e) {
            $this->notification()->error(
                $title = __('Error!'),
                $description = __('Oops, could not delete the selected tags') . '!' . $e->getMessage()
            );
        }
    }


    public function getBulkDisabledProperty()
    {
        return count($this->bulkSelected) === 0;
    }

    public function updatedBulkSelected()
    {
        // Debouncing in the view prevents excessive re-renders
    }



    public function updatedEditTagName()
    {
        //Capitalize first letter of the strings, make sure that for German accents remain, using the mb_strupper function
        $this->editTag['name'] = app()->getLocale() == 'de' ? mb_strtoupper(mb_substr($this->editTag['name'], 0, 1)) : ucfirst($this->editTag['name']);
        $this->validateOnly('editTag.name');
    }


    public function updatedEditTagLanguage()
    {
        if ($this->editTag['language'] != $this->initTag['language']) {
            $this->editTagLanguageChanged = true;
            $this->editTag['mergeTag'] = null;
        } else {
            $this->editTagLanguageChanged = false;
        }
        $this->validateOnly('editTag.name');

        // Get tags to choose for merging the tag
        $this->mergeTagOptions = $this->getMergeTagOptions($this->editTag['language']);
    }


    public function updatedEditTagCategory()
    {
        $this->newCategoryColor = Category::find($this->editTag['category'])->relatedColor ?? 'gray';
    }


    public function updatedEditTagMergeTag($value)
    {
        if (is_null($value)) {
            $this->editTagChanged = false;
        } else {
            $this->editTagChanged = true;
            $mergeTag = Tag::find($this->editTag['mergeTag']);
            $this->mergeName = $mergeTag->name;
            $this->mergeCategoryColor =
                $mergeTag->categories->map(function ($category) {
                    return $category->relatedColor;
                })->first();
        }
    }

    /**
     * Update the tag
     *
     * @param  mixed $translationId
     * @return void
     */
    public function updateTag()
    {
        // CRITICAL: Authorize admin access for updating tag
        $this->authorizeAdminAccess();

        $this->validate();
        try {
            DB::transaction(function () {


                // Merging the tag into another tag
                if (isset($this->editTag['mergeTag']) && $this->editTag['mergeTag']) {
                    $mergeTagId = $this->editTag['mergeTag'];

                    // Update the taggables table, ignore records that that already have the tag
                    DB::table('taggable_taggables')
                        ->where('tag_id', $this->selectedTagId)
                        ->whereNotExists(function ($query) use ($mergeTagId) {
                            $query->select(DB::raw(1))
                                ->from('taggable_taggables as t2')
                                ->whereColumn('t2.taggable_id', 'taggable_taggables.taggable_id')
                                ->whereColumn('t2.taggable_type', 'taggable_taggables.taggable_type')
                                ->where('t2.tag_id', $mergeTagId);
                        })
                        ->update(['tag_id' => $mergeTagId]);


                    // The Tag model has a listener that also deletes associates models
                    // Also all contexts without any tags left are deleted
                    Tag::whereIn('tag_id', [$this->selectedTagId])->delete();
                    $this->resetForm();

                    $merged = true;
                } else {

                    // No merging, but update other fields
                    $tag = Tag::find($this->selectedTagId);
                    if ($tag) {
                        $tag->name = $this->editTag['name'];
                        $tag->save();

                        $locale = $tag->locale()->first();
                        if ($locale) {
                            $locale->locale = $this->editTag['language'];
                            $locale->comment = $this->editTag['comment'];
                            $locale->save();
                        }

                        $context = $tag->contexts()->first();
                        if ($context) {
                            $context->category_id = $this->editTag['category'];
                            $context->save();
                        }
                    }

                    $this->resetForm();
                    $this->resetPage();
                }

                if (isset($merged) && $merged === true) {
                    $this->notification()->success(
                        $title = __('Merged'),
                        $description = __('Tag').' '.__('is merged successfully!')
                    );
                } else {
                    $this->notification()->success(
                        $title = __('Saved'),
                        $description = __('Tag').' '.__('is saved successfully!')
                    );
                }

                $this->modalEditTag = false;


            });
        } catch (Exception $e) {
            $this->notification()->error(
                $title = __('Error!'),
                $description = __('Oops, we have an error: the tag was not saved!').' '.$e->getMessage()
            );
            return back();
        }
    }


    public function resetForm()
    {
        $this->reset([
            'selectedTagId',
            'selectedTag',
            'modalDeleteTag',
            'modalBulkDeleteTags',
            'modalEditTag',
            'countTotal',
            'countTotalTranslations',
            'editTagChanged',
            'editTagContextChanged',
            'confirmString',
        ]);

        $this->resetErrorBag();
    }


    public function resetBulkSelection()
    {
        $this->bulkSelected = [];

        $this->notification()->info(
            $title = __('Selection cleared'),
            $description = __('Bulk selection has been reset.')
        );
    }


    public function sortBy(string $field): void
    {
        if ($this->sortField === $field) {
            $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
        } else {
            $this->sortDirection = 'asc';
        }

        $this->sortField = $field;
        $this->resetPage();
    }


    public function searchTags()
    {
        $this->resetPage();
        $this->dispatch('scroll-to-top');
    }


    public function handleSearchEnter()
    {
        if (!$this->showModal) {
            $this->searchTags();
            $this->resetPage();
        }
    }


    public function resetSearch()
    {
        $this->search = '';
        $this->searchTags();
        $this->dispatch('scroll-to-top');
    }


    public function updatedPage()
    {
        $this->dispatch('scroll-to-top');
    }


    public function updatingSearch()
    {
        $this->resetPage();
    }

    public function updatingPerPage()
    {
        $this->resetPage();
    }



    public function render()
    {
        // Build the base query with proper eager loading
        $tagsQuery = Tag::with([
            'locale',
            'contexts.category.translations' => function ($query) {
                $query->where('locale', app()->getLocale());
            }
        ])
            ->withCount(['users', 'organizations', 'banks']);

        // Apply search if provided - get all tags that share context with matching tags
        if ($this->search) {
            // First, find all context IDs that contain tags matching the search
            $matchingContextIds = DB::table('taggable_tags')
                ->join('taggable_locale_context', 'taggable_tags.tag_id', '=', 'taggable_locale_context.tag_id')
                ->leftJoin('taggable_locales', 'taggable_tags.tag_id', '=', 'taggable_locales.taggable_tag_id')
                ->where(function ($query) {
                    $query->where('taggable_tags.name', 'like', '%' . $this->search . '%')
                        ->orWhere('taggable_tags.tag_id', 'like', '%' . $this->search . '%')
                        ->orWhere('taggable_locales.comment', 'like', '%' . $this->search . '%');
                })
                ->distinct()
                ->pluck('taggable_locale_context.context_id');

            // Then filter tags to only those in the matching contexts
            if ($matchingContextIds->isNotEmpty()) {
                $tagsQuery->whereHas('contexts', function ($query) use ($matchingContextIds) {
                    $query->whereIn('taggable_contexts.id', $matchingContextIds);
                });
            } else {
                // If no contexts found, show no results
                $tagsQuery->whereRaw('1 = 0');
            }
        }

        // Join with context table for all queries to enable context-based ordering
        $tagsQuery->join('taggable_locale_context as tlc', 'taggable_tags.tag_id', '=', 'tlc.tag_id')
            ->join('taggable_contexts as tc', 'tlc.context_id', '=', 'tc.id')
            ->select('taggable_tags.*', 'tc.id as context_id', 'tc.updated_at as context_updated_at');

        // Apply sorting - always order by context first, then by the selected field
        switch ($this->sortField) {
            case 'locale':
                $tagsQuery->join('taggable_locales as tl', 'taggable_tags.tag_id', '=', 'tl.taggable_tag_id')
                    ->orderBy('tc.id') // Context first
                    ->orderBy('tl.locale', $this->sortDirection)
                    ->select('taggable_tags.*', 'tc.id as context_id', 'tc.updated_at as context_updated_at');
                break;

            case 'comment':
                $tagsQuery->join('taggable_locales as tl', 'taggable_tags.tag_id', '=', 'tl.taggable_tag_id')
                    ->orderBy('tc.id') // Context first
                    ->orderBy('tl.comment', $this->sortDirection)
                    ->select('taggable_tags.*', 'tc.id as context_id', 'tc.updated_at as context_updated_at');
                break;

            case 'updated_at':
                $tagsQuery->join('taggable_locales as tl', 'taggable_tags.tag_id', '=', 'tl.taggable_tag_id')
                    ->orderBy('tc.id') // Context first
                    ->orderBy('tl.updated_at', $this->sortDirection)
                    ->select('taggable_tags.*', 'tc.id as context_id', 'tc.updated_at as context_updated_at');
                break;

            case 'profiles':
                $tagsQuery->orderBy('tc.id') // Context first
                    ->orderByRaw('(users_count + organizations_count + banks_count) ' . $this->sortDirection);
                break;

            case 'category':
                $tagsQuery->join('categories as c', 'tc.category_id', '=', 'c.id')
                    ->join('category_translations as ct', function ($join) {
                        $join->on('c.id', '=', 'ct.category_id')
                            ->where('ct.locale', app()->getLocale());
                    })
                    ->orderBy('tc.id') // Context first
                    ->orderBy('ct.name', $this->sortDirection)
                    ->select('taggable_tags.*', 'tc.id as context_id', 'tc.updated_at as context_updated_at');
                break;

            case 'name':
                $tagsQuery->orderBy('tc.id') // Context first
                    ->orderBy('taggable_tags.name', $this->sortDirection);
                break;

            case 'tag_id':
                $tagsQuery->orderBy('tc.id') // Context first
                    ->orderBy('taggable_tags.tag_id', $this->sortDirection);
                break;

            default:
                // Default: order by context, then by context updated_at (most recent context activity first)
                $tagsQuery->orderBy('tc.id')
                    ->orderBy('tc.updated_at', 'desc');
                break;
        }

        // Within each context, also order by locale priority (en first, then others alphabetically)
        // This ensures consistent ordering within each tag group
        $tagsQuery->leftJoin('taggable_locales as tl_priority', 'taggable_tags.tag_id', '=', 'tl_priority.taggable_tag_id')
            ->orderByRaw("CASE WHEN tl_priority.locale = 'en' THEN 0 ELSE 1 END")
            ->orderBy('tl_priority.locale');

        // Paginate the results
        $tagsPaginator = $tagsQuery->paginate($this->perPage);

        // Group the results by context for display (they should already be ordered correctly)
        $groupedTags = $tagsPaginator->getCollection()->groupBy('context_id');

        return view('livewire.tags.manage', [
            'tags' => $tagsPaginator,
            'groupedTags' => $groupedTags
        ]);
    }
}
