<?php

namespace App\Http\Livewire;

use App\Events\ProfileSwitchEvent;
use App\Models\Admin;
use App\Models\Bank;
use App\Models\Organization;
use App\Models\User;
use App\Traits\SwitchGuardTrait;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Livewire\Component;
use Log;
use Stevebauman\Location\Facades\Location as IpLocation;
use WireUi\Traits\WireUiActions;

class SwitchProfile extends Component
{
    use WireUiActions;
    use SwitchGuardTrait;

    protected $user;
    public $userName;
    public $userProfiles = [];
    public $userProfileIndex;
    public $notifySwitchProfile;
    public $activeProfile = [];
    public $profilesWithUnread = [];

    /**
     * Get the event listeners for the component.
     * Listens for the ProfileSwitchEvent event on the switch-profile.{$userId} private Echo channel. When this event is fired, the notifySwitchProfile method of the component will be called.
     * @return array
     */
    protected function getListeners()
    {
        $userId = Auth::guard('web')->id(); // Get the authenticated user's ID for lowest level identification

        return [
            "echo-private:switch-profile.{$userId},ProfileSwitchEvent" => 'notifySwitchProfile',
        ];
    }


    public function mount()
    {

        //TODO NEXT: Add middleware seciruty!

        $this->user = Auth::guard('web')->user();
        $this->userName = $this->user->name;

        // Eager load profile relationships
        $userWithRelations = User::with([
            'organizations',
            'banksManaged',
            'admins'
            ])->find($this->user->id);

        
        // Filter out removed (soft-deleted) items
        $organizations = $userWithRelations->organizations->filter(
            fn ($organizations) => $organizations->deleted_at === null || $organizations->deleted_at > now()
        );
        $banks = $userWithRelations->banksManaged->filter(
            fn ($bank) => $bank->deleted_at === null || $bank->deleted_at > now()
        );
        $admins = $userWithRelations->admins->filter(
            fn ($admin) => $admin->deleted_at === null || $admin->deleted_at > now()
        );


        // Merge profiles  collections
        $profiles = $organizations
            ->concat($banks)
            ->concat($admins);

        // Map the merged collection to the desired structure
        $this->userProfiles = $profiles->map(function ($profile) {
            if ($profile instanceof Organization) {
                $type = 'organization';
            } elseif ($profile instanceof Bank) {
                $type = 'bank';
            } elseif ($profile instanceof Admin) {
                $type = 'admin';
            } else {
                $type = 'unknown';
            }
        
            return [
                'id' => $profile->id,
                'type' => $type,
                'name' => $profile->name,
                'photo' => $profile->profile_photo_path,
            ];
        })->values()->toArray();

        // Find the index corresponding to the current session profile
        $activeType = session('activeProfileType');
        $activeId = session('activeProfileId');

        if ($activeType === \App\Models\User::class && $activeId === $this->user->id) {
            $this->userProfileIndex = null; // Main user profile
        } else {
            foreach ($this->userProfiles as $index => $profile) {
                $profileClassName = 'App\\Models\\' . ucfirst($profile['type']);
                if ($profileClassName === $activeType && $profile['id'] === $activeId) {
                    $this->userProfileIndex = $index;
                    break;
                }
            }
        }
        // If no match found, it defaults to null (main user)

        // Load unread status for all profiles
        $this->loadUnreadStatus();
    }

    // Computed property to format options for WireUI select
    public function getSelectOptionsProperty(): array
    {
        $options = [
            // Main user option (value null)
            ['value' => null, 'label' => Str::limit($this->userName, 25, '...')]
        ];

        // Add other profiles from the loop
        foreach ($this->userProfiles as $index => $profile) {
            $options[] = [
                'value' => $index, // The original index is the value
                'label' => Str::limit($profile['name'], 25, '...') // The limited name is the label
            ];
        }
        return $options;
    }


    public function profileSelected($index = null)
    {
        $this->userProfileIndex = $index;
        $this->switchProfile();
    }


    public function switchProfile()
    {
        $index = $this->userProfileIndex;

        // If index is missing or out of range, switch to the auth user without login
        if ($index === null || !isset($this->userProfiles[$index])) {
            return $this->switchToAuthUser();
        }
        
        $profileArray = $this->userProfiles[$index];
        $profileType = ucfirst($profileArray['type']);
        $profileId   = $profileArray['id'];
        $profileClass = $profile = "App\\Models\\$profileType";
        $profile = $profileClass::find($profileId);

        // If the user is switching to their own User profile, switch without login
        if ($profileType === 'User' && (int) $profileId ===  Auth::guard('web')->id()) {
            return $this->switchToAuthUser();
        }
        
        // If switching to an Admin profile, check if the user is authenticated as an Admin 
        elseif ($profileType === 'Admin' ) {
            // Redirect to admin login page
            session([
                'intended_profile_switch_type' => $profileType,
                'intended_profile_switch_id' => $profileId,
            ]);            
            $this->redirect(route('admin.login')); 
            return; // Explicitly stop further execution
        }
        // If switching to an Bank profile, check if the user is authenticated as an Bank 
        elseif ($profileType === 'Bank' ) {
            // Redirect to bank login page
            session([
                'intended_profile_switch_type' => $profileType,
                'intended_profile_switch_id' => $profileId,
            ]);            
            $this->redirect(route('bank.login')); 
            return; // Explicitly stop further execution
        }

        // No Bank or Admin profile: without login
        // Otherwise, attempt to find and validate the model
        $profileClassName = 'App\\Models\\' . $profileType;
        $profileModel     = $profileClassName::find($profileId);
        $newGuard = strtolower($profileType);

        // If the model doesn't exist or the user doesn't own it, fall back with a warning
        // Use userOwnsProfile instead of can() to avoid cross-guard protection during switching
        if (!$profileModel || !\App\Helpers\ProfileAuthorizationHelper::userOwnsProfile($profileModel) || !class_exists($profileClassName)) {
            return $this->fallbackToAuthUser();
        }

        $this->switchGuard($newGuard, $profileModel);

        // Switch to the chosen profile
        session([
            'activeProfileType'  => $profileClassName,
            'activeProfileId'    => $profileModel->id,
            'activeProfileName'  => $profileModel->name,
            'activeProfilePhoto' => $profileModel->profile_photo_path,
            'profile-switched-notification' => true,
        ]);

        // Re-activate profile if inactive
        if (timebank_config('profile_inactive.re-activate_at_login')) {
            if (!$profileModel->isActive()) {
                $profileModel->inactive_at = null;
                $profileModel->save();
                info(class_basename($profileClassName)  . ' ' . 're-activated: ' . $profileModel->name);
            }
        }

        // Fire the event, redirect, etc.
        event(new ProfileSwitchEvent($profileModel));
        $this->redirect(route('main')); // Use Livewire's redirect method
        return; // Explicitly stop further execution
    }

    /**
     * Switch to the authenticated user's own profile without logging in.
     */
    protected function switchToAuthUser()
    {
        $this->logoutNonWebGuards();

        $user = Auth::guard('web')->user();

        session([
            'activeProfileType'  => \App\Models\User::class,
            'activeProfileId'    => $user->id,
            'activeProfileName'  => $user->name,
            'activeProfilePhoto' => $user->profile_photo_path,
            'active_guard'       => 'web', // Explicitly set active guard
            'profile-switched-notification'   => true,
        ]);
        event(new ProfileSwitchEvent($user));
        $this->redirect(route('main')); // Use Livewire's redirect method
        return; // Explicitly stop further execution
    }


    /**
     * If user tampered with the front-end, we fall back to the default user profile.
     */
    protected function fallbackToAuthUser()
    {
        $this->logoutNonWebGuards();

        $user = Auth::guard('web')->user();
        Session([
            'activeProfileType'  => \App\Models\User::class,
            'activeProfileId'    => $user->id,
            'activeProfileName'  => $user->name,
            'activeProfilePhoto' => $user->profile_photo_path,
            'activeProfileAccounts' => $user->accounts()->pluck('id')->toArray(),
            'active_guard'       => 'web', // Explicitly set active guard
        ]);

        $activeProfile = [
            'userId' => $user->id,
            'type'   => session('activeProfileType'),
            'id'     => session('activeProfileId'),
            'name'   => session('activeProfileName'),
            'photo'  => session('activeProfilePhoto'),
        ];
        $warningMessage = 'Unauthorized profile switch attempt';
        $this->logAndReport($warningMessage);
        session()->flash('error', __($warningMessage) . '. ' . __('This event has been logged') . '!');
        session(['unauthorizedAction' => __($warningMessage) . '. ' . __('This event has been logged') . '!']);
        return event(new ProfileSwitchEvent($activeProfile));
    }

    public function notifySwitchProfile($activeProfile)
    {
        $this->notifySwitchProfile = true;

        Session([
                    // 'active_guard'       => $activeProfile['guard'],
                    'activeProfileType' => $activeProfile['type'],
                    'activeProfileId' => $activeProfile['id'],
                    'activeProfileName' => $activeProfile['name'],
                    'activeProfilePhoto' => $activeProfile['photo'],
                    'profile-switched-notification' => true,
                ]);

        return redirect()->route('main');
    }


    /**
     * Logs a warning message and reports it via email to the system administrator.
     *
     * This method logs a warning message with detailed information about the event,
     * including account details, user details, IP address, and location. It also
     * sends an email to the system administrator with the same information.
     */
    private function logAndReport($warningMessage, $error = '')
    {
        $ip = request()->ip();
        $ipLocationInfo = IpLocation::get($ip);

        // Escape ipLocation errors when not in production
        if (!$ipLocationInfo || App::environment(['local', 'development', 'staging'])) {
            $ipLocationInfo = (object) [
                'cityName' => 'local City',
                'regionName' => 'local Region',
                'countryName' => 'local Country',
            ];
        }
        $eventTime = now()->toDateTimeString();

        // Log this event and mail to admin
        Log::warning($warningMessage, [
            'userId' => Auth::guard('web')->id(),
            'userName' => Auth::user()->name,
            'activeProfileId' => session('activeProfileId'),
            'activeProfileType' => session('activeProfileType'),
            'activeProfileName' => session('activeProfileName'),
            'IP address' => $ip,
            'IP location' => $ipLocationInfo->cityName . ', ' . $ipLocationInfo->regionName . ', ' . $ipLocationInfo->countryName,
            'Event Time' => $eventTime,
            'Message' => $error,
        ]);
        Mail::raw(
            $warningMessage . '.' . "\n\n" .
            'User ID: ' . Auth::guard('web')->id() . "\n" . 'User Name: ' . Auth::guard('web')->user()->name . "\n" .
            'Active Profile ID: ' . session('activeProfileId') . "\n" .
            'Active Profile Type: ' . session('activeProfileType') . "\n" .
            'Active Profile Name: ' . session('activeProfileName') . "\n" .
            'IP address: ' . $ip . "\n" .
            'IP location: ' . $ipLocationInfo->cityName . ', ' . $ipLocationInfo->regionName . ', ' . $ipLocationInfo->countryName . "\n" .
            'Event Time: ' . $eventTime . "\n\n" .
            $error,
            function ($message) use ($warningMessage) {
                $message->to(timebank_config('mail.system_admin.email'))->subject($warningMessage);
            },
        );

        session()->flash('error', __($warningMessage) . '. ' . __('This event has been logged and reported to our system administrator') . '.');
    }

    /**
     * Load unread status for all switchable profiles
     */
    private function loadUnreadStatus()
    {
        $webUser = Auth::guard('web')->user();
        if (!$webUser) {
            return;
        }

        // Collect all profile IDs
        $profileIds = [
            'user' => [$webUser->id],
            'admin' => [],
            'bank' => [],
            'organization' => [],
        ];

        foreach ($this->userProfiles as $profile) {
            $profileIds[$profile['type']][] = $profile['id'];
        }

        // Check for unread messages
        $this->profilesWithUnread = $this->checkUnreadMessages($profileIds);
    }

    /**
     * Check which profiles have unread messages (boolean check only)
     * Returns array like: ['user' => true, 'admin' => [2 => true], 'bank' => [3 => false], ...]
     */
    private function checkUnreadMessages(array $profileIds): array
    {
        $result = [
            'user' => false,
            'admin' => [],
            'bank' => [],
            'organization' => [],
        ];

        // Collect all profile type/id pairs
        $profilePairs = [];
        foreach ($profileIds as $type => $ids) {
            foreach ($ids as $id) {
                $modelClass = $this->getModelClass($type);
                $profilePairs[] = ['type' => $modelClass, 'id' => $id];
            }
        }

        if (empty($profilePairs)) {
            return $result;
        }

        // Use parameter binding with IN clause for better escaping
        $typeIds = collect($profilePairs)->groupBy('type')->map(function ($items) {
            return $items->pluck('id')->toArray();
        })->toArray();

        // Check each type separately and combine results
        foreach ($typeIds as $modelClass => $ids) {
            $unreadForType = DB::table('wirechat_participants as p')
                ->select('p.participantable_type', 'p.participantable_id')
                ->join('wirechat_conversations as c', 'p.conversation_id', '=', 'c.id')
                ->where('p.participantable_type', '=', $modelClass)
                ->whereIn('p.participantable_id', $ids)
                ->whereNull('p.deleted_at')
                ->whereExists(function ($query) {
                    $query->select(DB::raw(1))
                        ->from('wirechat_messages as m')
                        ->whereColumn('m.conversation_id', 'p.conversation_id')
                        ->whereNull('m.deleted_at')
                        ->where(function ($q) {
                            $q->whereNull('p.conversation_read_at')
                              ->orWhereColumn('m.created_at', '>', 'p.conversation_read_at');
                        })
                        ->where(function ($q) {
                            $q->where('m.sendable_id', '!=', DB::raw('p.participantable_id'))
                              ->orWhere('m.sendable_type', '!=', DB::raw('p.participantable_type'));
                        });
                })
                ->get();

            foreach ($unreadForType as $row) {
                $type = $this->getTypeFromModelClass($row->participantable_type);
                $id = $row->participantable_id;

                if ($type === 'user') {
                    $result['user'] = true;
                } else {
                    $result[$type][$id] = true;
                }
            }
        }

        return $result;
    }

    /**
     * Map profile type to model class (return with single backslash for database comparison)
     */
    private function getModelClass(string $type): string
    {
        return match($type) {
            'user' => 'App\Models\User',
            'admin' => 'App\Models\Admin',
            'bank' => 'App\Models\Bank',
            'organization' => 'App\Models\Organization',
        };
    }

    /**
     * Map model class to profile type
     */
    private function getTypeFromModelClass(string $class): string
    {
        return match($class) {
            'App\\Models\\User', 'App\Models\User' => 'user',
            'App\\Models\\Admin', 'App\Models\Admin' => 'admin',
            'App\\Models\\Bank', 'App\Models\Bank' => 'bank',
            'App\\Models\\Organization', 'App\Models\Organization' => 'organization',
            default => 'unknown'
        };
    }

    public function render()
    {
        return view('livewire.switch-profile');
    }
}
